<commit_before>package obj

import (
	"fmt"
	"reflect"
	"testing"

	. "github.com/smartystreets/goconvey/convey"

	"github.com/polydawn/refmt/obj2/atlas"
	. "github.com/polydawn/refmt/tok"
	"github.com/polydawn/refmt/tok/fixtures"
)

var skipMe = fmt.Errorf("skipme")

type marshalResults struct {
	title string
	// Yields a value to hand to the marshaller.
	// A func returning a wildcard is used rather than just an `interface{}`, because `&target` conveys very different type information.
	valueFn func() interface{}

	expectErr error
	errString string
}
type unmarshalResults struct {
	title string
	// Yields the handle we should give to the unmarshaller to fill.
	// Like `valueFn`, the indirection here is to help
	slotFn func() interface{}

	// Yields the value we will compare the unmarshal result against.
	// A func returning a wildcard is used rather than just an `interface{}`, because `&target` conveys very different type information.
	valueFn   func() interface{}
	expectErr error
	errString string
}

type tObjStr struct {
	X string
}

type tObjStr2 struct {
	X string
	Y string
}

var objFixtures = []struct {
	title string

	// The serial sequence of tokens the value is isomorphic to.
	sequence fixtures.Sequence

	// The suite of mappings to use.
	atlas atlas.Atlas

	// The results to expect from various marshalling starting points.
	// This is a slice because we occasionally have several different kinds of objects
	// which we expect will converge on the same token fixture given the same atlas.
	marshalResults []marshalResults

	// The results to expect from various unmarshal situations.
	// This is a slice because unmarshal may have different outcomes (usually,
	// erroring vs not) depending on the type of value it was given to populate.
	unmarshalResults []unmarshalResults
}{
	{title: "string literal",
		sequence: fixtures.SequenceMap["flat string"],
		marshalResults: []marshalResults{
			{title: "from string literal",
				valueFn: func() interface{} { str := "value"; return str }},
			{title: "from string in iface slot",
				valueFn: func() interface{} { var iface interface{}; iface = "value"; return iface }},
		},
		unmarshalResults: []unmarshalResults{
			{title: "into string",
				slotFn:    func() interface{} { var str string; return str },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf("")}},
			{title: "into *string",
				slotFn:  func() interface{} { var str string; return &str },
				valueFn: func() interface{} { str := "value"; return str }},
			{title: "into wildcard",
				slotFn:    func() interface{} { var v interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(interface{}(nil))}},
			{title: "into *wildcard",
				slotFn:  func() interface{} { var v interface{}; return &v },
				valueFn: func() interface{} { str := "value"; return str }},
			{title: "into map[str]iface",
				slotFn:    func() interface{} { var v map[string]interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(map[string]interface{}(nil))}},
			{title: "into *map[str]iface",
				slotFn:    func() interface{} { var v map[string]interface{}; return &v },
				expectErr: ErrUnmarshalIncongruent{Token{Type: TString, Str: "value"}, reflect.ValueOf(map[string]interface{}(nil))}},
			{title: "into []iface",
				slotFn:    func() interface{} { var v []interface{}; return v },
				expectErr: skipMe},
			{title: "into *[]iface",
				slotFn:    func() interface{} { var v []interface{}; return &v },
				expectErr: skipMe},
		},
	},
	{title: "object with one string field, with atlas entry",
		sequence: fixtures.SequenceMap["single row map"],
		atlas: atlas.MustBuild(
			atlas.BuildEntry(tObjStr{}).StructMap().
				AddField("X", atlas.StructMapEntry{SerialName: "key"}).
				Complete(),
		),
		marshalResults: []marshalResults{
			{title: "from object with one field",
				valueFn: func() interface{} { return tObjStr{"value"} }},
			{title: "from map[str]iface with one entry",
				valueFn: func() interface{} { return map[string]interface{}{"key": "value"} }},
			{title: "from map[str]str with one entry",
				valueFn: func() interface{} { return map[string]string{"key": "value"} }},
		},
		unmarshalResults: []unmarshalResults{
			{title: "into string",
				slotFn:    func() interface{} { var str string; return str },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf("")}},
			{title: "into *string",
				slotFn:    func() interface{} { var str string; return &str },
				expectErr: ErrUnmarshalIncongruent{Token{Type: TMapOpen, Length: 1}, reflect.ValueOf("")}},
			{title: "into wildcard",
				slotFn:    func() interface{} { var v interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(interface{}(nil))}},
			{title: "into *wildcard",
				slotFn:  func() interface{} { var v interface{}; return &v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value"} }},
			{title: "into map[str]iface",
				slotFn:    func() interface{} { var v map[string]interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(map[string]interface{}(nil))}},
			{title: "into made map[str]iface",
				slotFn:  func() interface{} { v := make(map[string]interface{}); return v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value"} }},
			{title: "into *map[str]iface",
				slotFn:  func() interface{} { var v map[string]interface{}; return &v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value"} }},
			{title: "into *map[str]str",
				slotFn:  func() interface{} { var v map[string]string; return &v },
				valueFn: func() interface{} { return map[string]string{"key": "value"} }},
			{title: "into []iface",
				slotFn:    func() interface{} { var v []interface{}; return v },
				expectErr: skipMe},
			{title: "into *[]iface",
				slotFn:    func() interface{} { var v []interface{}; return &v },
				expectErr: skipMe},
		},
	},
	{title: "object with two string fields, with atlas entry",
		sequence: fixtures.SequenceMap["duo row map"],
		atlas: atlas.MustBuild(
			atlas.BuildEntry(tObjStr2{}).StructMap().
				AddField("X", atlas.StructMapEntry{SerialName: "key"}).
				AddField("Y", atlas.StructMapEntry{SerialName: "k2"}).
				Complete(),
		),
		marshalResults: []marshalResults{
			{title: "from object with two fields",
				valueFn: func() interface{} { return tObjStr2{"value", "v2"} }},
		},
		unmarshalResults: []unmarshalResults{
			{title: "into string",
				slotFn:    func() interface{} { var str string; return str },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf("")}},
			{title: "into *string",
				slotFn:    func() interface{} { var str string; return &str },
				expectErr: ErrUnmarshalIncongruent{Token{Type: TMapOpen, Length: 2}, reflect.ValueOf("")}},
			{title: "into wildcard",
				slotFn:    func() interface{} { var v interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(interface{}(nil))}},
			{title: "into *wildcard",
				slotFn:  func() interface{} { var v interface{}; return &v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value", "k2": "v2"} }},
			{title: "into map[str]iface",
				slotFn:    func() interface{} { var v map[string]interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(map[string]interface{}(nil))}},
			{title: "into made map[str]iface",
				slotFn:  func() interface{} { v := make(map[string]interface{}); return v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value", "k2": "v2"} }},
			{title: "into *map[str]iface",
				slotFn:  func() interface{} { var v map[string]interface{}; return &v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value", "k2": "v2"} }},
			{title: "into *map[str]str",
				slotFn:  func() interface{} { var v map[string]string; return &v },
				valueFn: func() interface{} { return map[string]string{"key": "value", "k2": "v2"} }},
			{title: "into []iface",
				slotFn:    func() interface{} { var v []interface{}; return v },
				expectErr: skipMe},
			{title: "into *[]iface",
				slotFn:    func() interface{} { var v []interface{}; return &v },
				expectErr: skipMe},
		},
	},
}

func TestMarshaller(t *testing.T) {
	// Package all the values from one step into a struct, just so that
	// we can assert on them all at once and make one green checkmark render per step.
	// Stringify the token first so extraneous fields in the union are hidden.
	type step struct {
		tok string
		err error
	}

	Convey("Marshaller suite:", t, func() {
		for _, tr := range objFixtures {
			Convey(fmt.Sprintf("%q fixture sequence:", tr.title), func() {
				for _, trr := range tr.marshalResults {
					Convey(fmt.Sprintf("working %s (%T):", trr.title, trr.valueFn()), func() {

						// Set up marshaller.
						marshaller := NewMarshaler(tr.atlas)
						marshaller.Bind(trr.valueFn())

						Convey("Steps...", func() {
							// Run steps until the marshaller says done or error.
							// For each step, assert the token matches fixtures;
							// when error and expected one, skip token check on that step
							// and finalize with the assertion.
							// If marshaller doesn't stop when we expected it to based
							// on fixture length, let it keep running three more steps
							// so we get that much more debug info.
							var done bool
							var err error
							var tok Token
							expectSteps := len(tr.sequence.Tokens) - 1
							for nStep := 0; nStep < expectSteps+3; nStep++ {
								done, err = marshaller.Step(&tok)
								if err != nil && trr.expectErr != nil {
									Convey("Result (error expected)", func() {
										So(err.Error(), ShouldResemble, trr.expectErr.Error())
									})
									return
								}
								if nStep <= expectSteps {
									So(
										step{tok.String(), err},
										ShouldResemble,
										step{tr.sequence.Tokens[nStep].String(), nil},
									)
								} else {
									So(
										step{tok.String(), err},
										ShouldResemble,
										step{Token{}.String(), fmt.Errorf("overshoot")},
									)
								}
								if done {
									Convey("Result (halted correctly)", func() {
										So(nStep, ShouldEqual, expectSteps)
									})
									return
								}
							}
						})
					})
				}
			})
		}
	})
}

func TestUnmarshaller(t *testing.T) {
	// Package all the values from one step into a struct, just so that
	// we can assert on them all at once and make one green checkmark render per step.
	// Stringify the token first so extraneous fields in the union are hidden.
	type step struct {
		tok  string
		err  error
		done bool
	}

	Convey("Unmarshaller suite:", t, func() {
		for _, tr := range objFixtures {
			Convey(fmt.Sprintf("%q fixture sequence:", tr.title), func() {
				for _, trr := range tr.unmarshalResults {
					maybe := Convey
					if trr.expectErr == skipMe {
						maybe = SkipConvey
					}
					maybe(fmt.Sprintf("targetting %s (%T):", trr.title, trr.slotFn()), func() {
						// Grab slot.
						slot := trr.slotFn()

						// Set up unmarshaller.
						unmarshaller := NewUnmarshaler(tr.atlas)
						err := unmarshaller.Bind(slot)
						if err != nil && trr.expectErr != nil {
							Convey("Result (error expected)", func() {
								So(err.Error(), ShouldResemble, trr.expectErr.Error())
							})
							return
						}

						Convey("Steps...", func() {
							// Run steps.
							// This is less complicated than the marshaller test
							// because we know exactly when we'll run out of them.
							var done bool
							var err error
							expectSteps := len(tr.sequence.Tokens) - 1
							for nStep, tok := range tr.sequence.Tokens {
								done, err = unmarshaller.Step(&tok)
								if err != nil && trr.expectErr != nil {
									Convey("Result (error expected)", func() {
										So(err.Error(), ShouldResemble, trr.expectErr.Error())
									})
									return
								}
								if nStep == expectSteps {
									So(
										step{tok.String(), err, done},
										ShouldResemble,
										step{tr.sequence.Tokens[nStep].String(), nil, true},
									)
								} else {
									So(
										step{tok.String(), err, done},
										ShouldResemble,
										step{tr.sequence.Tokens[nStep].String(), nil, false},
									)
								}
							}

							Convey("Result", func() {
								// Get value back out.  Some reflection required to get around pointers.
								rv := reflect.ValueOf(slot)
								if rv.Kind() == reflect.Ptr {
									rv = rv.Elem()
								}
								So(rv.Interface(), ShouldResemble, trr.valueFn())
							})
						})
					})
				}
			})
		}
	})
}
<commit_msg>Add test that wildcard marshal machine indirects pointers correctly.  Works.<commit_after>package obj

import (
	"fmt"
	"reflect"
	"testing"

	. "github.com/smartystreets/goconvey/convey"

	"github.com/polydawn/refmt/obj2/atlas"
	. "github.com/polydawn/refmt/tok"
	"github.com/polydawn/refmt/tok/fixtures"
)

var skipMe = fmt.Errorf("skipme")

type marshalResults struct {
	title string
	// Yields a value to hand to the marshaller.
	// A func returning a wildcard is used rather than just an `interface{}`, because `&target` conveys very different type information.
	valueFn func() interface{}

	expectErr error
	errString string
}
type unmarshalResults struct {
	title string
	// Yields the handle we should give to the unmarshaller to fill.
	// Like `valueFn`, the indirection here is to help
	slotFn func() interface{}

	// Yields the value we will compare the unmarshal result against.
	// A func returning a wildcard is used rather than just an `interface{}`, because `&target` conveys very different type information.
	valueFn   func() interface{}
	expectErr error
	errString string
}

type tObjStr struct {
	X string
}

type tObjStr2 struct {
	X string
	Y string
}

var objFixtures = []struct {
	title string

	// The serial sequence of tokens the value is isomorphic to.
	sequence fixtures.Sequence

	// The suite of mappings to use.
	atlas atlas.Atlas

	// The results to expect from various marshalling starting points.
	// This is a slice because we occasionally have several different kinds of objects
	// which we expect will converge on the same token fixture given the same atlas.
	marshalResults []marshalResults

	// The results to expect from various unmarshal situations.
	// This is a slice because unmarshal may have different outcomes (usually,
	// erroring vs not) depending on the type of value it was given to populate.
	unmarshalResults []unmarshalResults
}{
	{title: "string literal",
		sequence: fixtures.SequenceMap["flat string"],
		marshalResults: []marshalResults{
			{title: "from string literal",
				valueFn: func() interface{} { str := "value"; return str }},
			{title: "from *string",
				valueFn: func() interface{} { str := "value"; return &str }},
			{title: "from string in iface slot",
				valueFn: func() interface{} { var iface interface{}; iface = "value"; return iface }},
			{title: "from string in *iface slot",
				valueFn: func() interface{} { var iface interface{}; iface = "value"; return &iface }},
		},
		unmarshalResults: []unmarshalResults{
			{title: "into string",
				slotFn:    func() interface{} { var str string; return str },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf("")}},
			{title: "into *string",
				slotFn:  func() interface{} { var str string; return &str },
				valueFn: func() interface{} { str := "value"; return str }},
			{title: "into wildcard",
				slotFn:    func() interface{} { var v interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(interface{}(nil))}},
			{title: "into *wildcard",
				slotFn:  func() interface{} { var v interface{}; return &v },
				valueFn: func() interface{} { str := "value"; return str }},
			{title: "into map[str]iface",
				slotFn:    func() interface{} { var v map[string]interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(map[string]interface{}(nil))}},
			{title: "into *map[str]iface",
				slotFn:    func() interface{} { var v map[string]interface{}; return &v },
				expectErr: ErrUnmarshalIncongruent{Token{Type: TString, Str: "value"}, reflect.ValueOf(map[string]interface{}(nil))}},
			{title: "into []iface",
				slotFn:    func() interface{} { var v []interface{}; return v },
				expectErr: skipMe},
			{title: "into *[]iface",
				slotFn:    func() interface{} { var v []interface{}; return &v },
				expectErr: skipMe},
		},
	},
	{title: "object with one string field, with atlas entry",
		sequence: fixtures.SequenceMap["single row map"],
		atlas: atlas.MustBuild(
			atlas.BuildEntry(tObjStr{}).StructMap().
				AddField("X", atlas.StructMapEntry{SerialName: "key"}).
				Complete(),
		),
		marshalResults: []marshalResults{
			{title: "from object with one field",
				valueFn: func() interface{} { return tObjStr{"value"} }},
			{title: "from map[str]iface with one entry",
				valueFn: func() interface{} { return map[string]interface{}{"key": "value"} }},
			{title: "from map[str]str with one entry",
				valueFn: func() interface{} { return map[string]string{"key": "value"} }},
		},
		unmarshalResults: []unmarshalResults{
			{title: "into string",
				slotFn:    func() interface{} { var str string; return str },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf("")}},
			{title: "into *string",
				slotFn:    func() interface{} { var str string; return &str },
				expectErr: ErrUnmarshalIncongruent{Token{Type: TMapOpen, Length: 1}, reflect.ValueOf("")}},
			{title: "into wildcard",
				slotFn:    func() interface{} { var v interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(interface{}(nil))}},
			{title: "into *wildcard",
				slotFn:  func() interface{} { var v interface{}; return &v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value"} }},
			{title: "into map[str]iface",
				slotFn:    func() interface{} { var v map[string]interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(map[string]interface{}(nil))}},
			{title: "into made map[str]iface",
				slotFn:  func() interface{} { v := make(map[string]interface{}); return v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value"} }},
			{title: "into *map[str]iface",
				slotFn:  func() interface{} { var v map[string]interface{}; return &v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value"} }},
			{title: "into *map[str]str",
				slotFn:  func() interface{} { var v map[string]string; return &v },
				valueFn: func() interface{} { return map[string]string{"key": "value"} }},
			{title: "into []iface",
				slotFn:    func() interface{} { var v []interface{}; return v },
				expectErr: skipMe},
			{title: "into *[]iface",
				slotFn:    func() interface{} { var v []interface{}; return &v },
				expectErr: skipMe},
		},
	},
	{title: "object with two string fields, with atlas entry",
		sequence: fixtures.SequenceMap["duo row map"],
		atlas: atlas.MustBuild(
			atlas.BuildEntry(tObjStr2{}).StructMap().
				AddField("X", atlas.StructMapEntry{SerialName: "key"}).
				AddField("Y", atlas.StructMapEntry{SerialName: "k2"}).
				Complete(),
		),
		marshalResults: []marshalResults{
			{title: "from object with two fields",
				valueFn: func() interface{} { return tObjStr2{"value", "v2"} }},
		},
		unmarshalResults: []unmarshalResults{
			{title: "into string",
				slotFn:    func() interface{} { var str string; return str },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf("")}},
			{title: "into *string",
				slotFn:    func() interface{} { var str string; return &str },
				expectErr: ErrUnmarshalIncongruent{Token{Type: TMapOpen, Length: 2}, reflect.ValueOf("")}},
			{title: "into wildcard",
				slotFn:    func() interface{} { var v interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(interface{}(nil))}},
			{title: "into *wildcard",
				slotFn:  func() interface{} { var v interface{}; return &v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value", "k2": "v2"} }},
			{title: "into map[str]iface",
				slotFn:    func() interface{} { var v map[string]interface{}; return v },
				expectErr: ErrInvalidUnmarshalTarget{reflect.TypeOf(map[string]interface{}(nil))}},
			{title: "into made map[str]iface",
				slotFn:  func() interface{} { v := make(map[string]interface{}); return v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value", "k2": "v2"} }},
			{title: "into *map[str]iface",
				slotFn:  func() interface{} { var v map[string]interface{}; return &v },
				valueFn: func() interface{} { return map[string]interface{}{"key": "value", "k2": "v2"} }},
			{title: "into *map[str]str",
				slotFn:  func() interface{} { var v map[string]string; return &v },
				valueFn: func() interface{} { return map[string]string{"key": "value", "k2": "v2"} }},
			{title: "into []iface",
				slotFn:    func() interface{} { var v []interface{}; return v },
				expectErr: skipMe},
			{title: "into *[]iface",
				slotFn:    func() interface{} { var v []interface{}; return &v },
				expectErr: skipMe},
		},
	},
}

func TestMarshaller(t *testing.T) {
	// Package all the values from one step into a struct, just so that
	// we can assert on them all at once and make one green checkmark render per step.
	// Stringify the token first so extraneous fields in the union are hidden.
	type step struct {
		tok string
		err error
	}

	Convey("Marshaller suite:", t, func() {
		for _, tr := range objFixtures {
			Convey(fmt.Sprintf("%q fixture sequence:", tr.title), func() {
				for _, trr := range tr.marshalResults {
					Convey(fmt.Sprintf("working %s (%T):", trr.title, trr.valueFn()), func() {

						// Set up marshaller.
						marshaller := NewMarshaler(tr.atlas)
						marshaller.Bind(trr.valueFn())

						Convey("Steps...", func() {
							// Run steps until the marshaller says done or error.
							// For each step, assert the token matches fixtures;
							// when error and expected one, skip token check on that step
							// and finalize with the assertion.
							// If marshaller doesn't stop when we expected it to based
							// on fixture length, let it keep running three more steps
							// so we get that much more debug info.
							var done bool
							var err error
							var tok Token
							expectSteps := len(tr.sequence.Tokens) - 1
							for nStep := 0; nStep < expectSteps+3; nStep++ {
								done, err = marshaller.Step(&tok)
								if err != nil && trr.expectErr != nil {
									Convey("Result (error expected)", func() {
										So(err.Error(), ShouldResemble, trr.expectErr.Error())
									})
									return
								}
								if nStep <= expectSteps {
									So(
										step{tok.String(), err},
										ShouldResemble,
										step{tr.sequence.Tokens[nStep].String(), nil},
									)
								} else {
									So(
										step{tok.String(), err},
										ShouldResemble,
										step{Token{}.String(), fmt.Errorf("overshoot")},
									)
								}
								if done {
									Convey("Result (halted correctly)", func() {
										So(nStep, ShouldEqual, expectSteps)
									})
									return
								}
							}
						})
					})
				}
			})
		}
	})
}

func TestUnmarshaller(t *testing.T) {
	// Package all the values from one step into a struct, just so that
	// we can assert on them all at once and make one green checkmark render per step.
	// Stringify the token first so extraneous fields in the union are hidden.
	type step struct {
		tok  string
		err  error
		done bool
	}

	Convey("Unmarshaller suite:", t, func() {
		for _, tr := range objFixtures {
			Convey(fmt.Sprintf("%q fixture sequence:", tr.title), func() {
				for _, trr := range tr.unmarshalResults {
					maybe := Convey
					if trr.expectErr == skipMe {
						maybe = SkipConvey
					}
					maybe(fmt.Sprintf("targetting %s (%T):", trr.title, trr.slotFn()), func() {
						// Grab slot.
						slot := trr.slotFn()

						// Set up unmarshaller.
						unmarshaller := NewUnmarshaler(tr.atlas)
						err := unmarshaller.Bind(slot)
						if err != nil && trr.expectErr != nil {
							Convey("Result (error expected)", func() {
								So(err.Error(), ShouldResemble, trr.expectErr.Error())
							})
							return
						}

						Convey("Steps...", func() {
							// Run steps.
							// This is less complicated than the marshaller test
							// because we know exactly when we'll run out of them.
							var done bool
							var err error
							expectSteps := len(tr.sequence.Tokens) - 1
							for nStep, tok := range tr.sequence.Tokens {
								done, err = unmarshaller.Step(&tok)
								if err != nil && trr.expectErr != nil {
									Convey("Result (error expected)", func() {
										So(err.Error(), ShouldResemble, trr.expectErr.Error())
									})
									return
								}
								if nStep == expectSteps {
									So(
										step{tok.String(), err, done},
										ShouldResemble,
										step{tr.sequence.Tokens[nStep].String(), nil, true},
									)
								} else {
									So(
										step{tok.String(), err, done},
										ShouldResemble,
										step{tr.sequence.Tokens[nStep].String(), nil, false},
									)
								}
							}

							Convey("Result", func() {
								// Get value back out.  Some reflection required to get around pointers.
								rv := reflect.ValueOf(slot)
								if rv.Kind() == reflect.Ptr {
									rv = rv.Elem()
								}
								So(rv.Interface(), ShouldResemble, trr.valueFn())
							})
						})
					})
				}
			})
		}
	})
}
<|endoftext|>


<commit_before>package openapi3

import (
	"fmt"
	"strings"

	oas3 "github.com/getkin/kin-openapi/openapi3"
	"github.com/grokify/gotilla/os/osutil"
	"github.com/grokify/gotilla/type/maputil"
)

const (
	DescStatusIsEmpty    = 0
	DescStatusIsNotEmpty = 1
)

const defaultSep = " ~~~ "

// OperationPropertiesDescriptionStatus returns a set of
// operationIds and parameters with description status where `1`
// indicates a description and `0` indicates no descriptions.
// Descriptions for references aren't processed so they aren't
// analyzed and reported on. This returns a `MapStringMapStringInt`
// where the first key is the operationIds and the second key is the
// parameter name.
func (sm *SpecMore) OperationPropertiesDescriptionStatus() maputil.MapStringMapStringInt {
	descStatus := maputil.MapStringMapStringInt{}
	VisitOperations(sm.Spec, func(path, method string, op *oas3.Operation) {
		if op == nil {
			return
		}
		for _, paramRef := range op.Parameters {
			if paramRef == nil {
				continue
			}
			// Is a reference
			if len(strings.TrimSpace(paramRef.Ref)) > 0 {
				continue
			}
			// Is not a reference but has no value.
			if paramRef.Value == nil {
				continue
			}
			descTry := strings.TrimSpace(paramRef.Value.Description)
			if len(descTry) == 0 {
				descStatus.Set(op.OperationID, paramRef.Value.Name, DescStatusIsEmpty)
			} else {
				descStatus.Set(op.OperationID, paramRef.Value.Name, DescStatusIsNotEmpty)
			}
		}
	})
	return descStatus
}

// SchemaPropertiesDescriptionStatus returns a set of
// schema names and properties with description status where `1`
// indicates a description and `0` indicates no descriptions.
// Descriptions for references aren't processed so they aren't
// analyzed and reported on. This returns a `MapStringMapStringInt`
// where the first key is the component name and the second key is the
// property name.
func (sm *SpecMore) SchemaPropertiesDescriptionStatus() maputil.MapStringMapStringInt {
	descStatus := maputil.MapStringMapStringInt{}
	for schName, schRef := range sm.Spec.Components.Schemas {
		if len(schRef.Ref) > 0 || schRef.Value == nil {
			continue
		}
		for propName, propRef := range schRef.Value.Properties {
			if propRef == nil ||
				len(propRef.Ref) > 0 ||
				propRef.Value == nil {
				continue
			}
			desc := strings.TrimSpace(propRef.Value.Description)
			if len(desc) == 0 {
				descStatus.Set(schName, propName, DescStatusIsEmpty)
			} else {
				descStatus.Set(schName, propName, DescStatusIsNotEmpty)
			}
		}
	}
	return descStatus
}

func (sm *SpecMore) OperationParametersWithoutDescriptionsWriteFile(filename string) error {
	descStatus := sm.OperationPropertiesDescriptionStatus()
	missingDescPaths := descStatus.Flatten("#/paths/...", "/",
		maputil.MapStringMapStringIntFuncExactMatch(DescStatusIsEmpty),
		true, true)
	withCount1, withCount2 := descStatus.CountsWithVal(DescStatusIsNotEmpty, defaultSep)
	woutCount1, woutCount2 := descStatus.CountsWithVal(DescStatusIsEmpty, defaultSep)
	allCount1, allCount2 := descStatus.Counts(defaultSep)
	lines := []string{
		fmt.Sprintf("Operations Missing/Have/All [%d/%d/%d] Params Missing/Have/All [%d/%d/%d]",
			woutCount1, withCount1, allCount1,
			woutCount2, withCount2, allCount2),
	}
	lines = append(lines, missingDescPaths...)

	return osutil.CreateFileWithLines(filename, lines, "\n", true)
}

func (sm *SpecMore) SchemaPropertiesWithoutDescriptionsWriteFile(filename string) error {
	missing := sm.SchemaPropertiesDescriptionStatus()
	arr := missing.Flatten("#/components/schemas", "/",
		maputil.MapStringMapStringIntFuncExactMatch(DescStatusIsEmpty),
		true, true)
	withCount1, withCount2 := missing.CountsWithVal(DescStatusIsNotEmpty, defaultSep)
	woutCount1, woutCount2 := missing.CountsWithVal(DescStatusIsEmpty, defaultSep)
	allCount1, allCount2 := missing.Counts(defaultSep)
	lines := []string{
		fmt.Sprintf("Schemas Missing/Have/All [%d/%d/%d] Props Missing/Have/All [%d/%d/%d]",
			woutCount1, withCount1, allCount1,
			woutCount2, withCount2, allCount2),
	}
	lines = append(lines, arr...)

	return osutil.CreateFileWithLines(filename, lines, "\n", true)
}
<commit_msg>feat: openapi3: add OperationParametersDescriptionStatusCounts(), SchemaPropertiesDescriptionStatusCounts()<commit_after>package openapi3

import (
	"fmt"
	"strings"

	oas3 "github.com/getkin/kin-openapi/openapi3"
	"github.com/grokify/gotilla/os/osutil"
	"github.com/grokify/gotilla/type/maputil"
)

const (
	DescStatusIsEmpty    = 0
	DescStatusIsNotEmpty = 1
	DescStatusDefaultSep = " ~~~ "
)

// OperationParametersDescriptionStatus returns a set of
// operationIds and parameters with description status where `1`
// indicates a description and `0` indicates no descriptions.
// Descriptions for references aren't processed so they aren't
// analyzed and reported on. This returns a `MapStringMapStringInt`
// where the first key is the operationIds and the second key is the
// parameter name.
func (sm *SpecMore) OperationParametersDescriptionStatus() maputil.MapStringMapStringInt {
	descStatus := maputil.MapStringMapStringInt{}
	VisitOperations(sm.Spec, func(path, method string, op *oas3.Operation) {
		if op == nil {
			return
		}
		for _, paramRef := range op.Parameters {
			if paramRef == nil {
				continue
			}
			// Is a reference
			if len(strings.TrimSpace(paramRef.Ref)) > 0 {
				continue
			}
			// Is not a reference but has no value.
			if paramRef.Value == nil {
				continue
			}
			descTry := strings.TrimSpace(paramRef.Value.Description)
			if len(descTry) == 0 {
				descStatus.Set(op.OperationID, paramRef.Value.Name, DescStatusIsEmpty)
			} else {
				descStatus.Set(op.OperationID, paramRef.Value.Name, DescStatusIsNotEmpty)
			}
		}
	})
	return descStatus
}

// OperationParametersDescriptionStatusCounts returns operation parameter
// counts with descriptions, without descriptions, and total counts.
func (sm *SpecMore) OperationParametersDescriptionStatusCounts() (with, without, all int) {
	descStatus := sm.OperationParametersDescriptionStatus()
	_, with = descStatus.CountsWithVal(DescStatusIsNotEmpty, DescStatusDefaultSep)
	_, without = descStatus.CountsWithVal(DescStatusIsEmpty, DescStatusDefaultSep)
	_, all = descStatus.Counts(DescStatusDefaultSep)
	return
}

// SchemaPropertiesDescriptionStatus returns a set of
// schema names and properties with description status where `1`
// indicates a description and `0` indicates no descriptions.
// Descriptions for references aren't processed so they aren't
// analyzed and reported on. This returns a `MapStringMapStringInt`
// where the first key is the component name and the second key is the
// property name.
func (sm *SpecMore) SchemaPropertiesDescriptionStatus() maputil.MapStringMapStringInt {
	descStatus := maputil.MapStringMapStringInt{}
	for schName, schRef := range sm.Spec.Components.Schemas {
		if len(schRef.Ref) > 0 || schRef.Value == nil {
			continue
		}
		for propName, propRef := range schRef.Value.Properties {
			if propRef == nil ||
				len(propRef.Ref) > 0 ||
				propRef.Value == nil {
				continue
			}
			desc := strings.TrimSpace(propRef.Value.Description)
			if len(desc) == 0 {
				descStatus.Set(schName, propName, DescStatusIsEmpty)
			} else {
				descStatus.Set(schName, propName, DescStatusIsNotEmpty)
			}
		}
	}
	return descStatus
}

// SchemaPropertiesDescriptionStatusCounts returns schema property
// counts with descriptions, without descriptions, and total counts.
func (sm *SpecMore) SchemaPropertiesDescriptionStatusCounts() (with, without, all int) {
	descStatus := sm.SchemaPropertiesDescriptionStatus()
	_, with = descStatus.CountsWithVal(DescStatusIsNotEmpty, DescStatusDefaultSep)
	_, without = descStatus.CountsWithVal(DescStatusIsEmpty, DescStatusDefaultSep)
	_, all = descStatus.Counts(DescStatusDefaultSep)
	return
}

func (sm *SpecMore) OperationParametersWithoutDescriptionsWriteFile(filename string) error {
	descStatus := sm.OperationParametersDescriptionStatus()
	missingDescPaths := descStatus.Flatten("#/paths/...", "/",
		maputil.MapStringMapStringIntFuncExactMatch(DescStatusIsEmpty),
		true, true)
	withCount1, withCount2 := descStatus.CountsWithVal(DescStatusIsNotEmpty, DescStatusDefaultSep)
	woutCount1, woutCount2 := descStatus.CountsWithVal(DescStatusIsEmpty, DescStatusDefaultSep)
	allCount1, allCount2 := descStatus.Counts(DescStatusDefaultSep)
	lines := []string{
		fmt.Sprintf("Operations Missing/Have/All [%d/%d/%d] Params Missing/Have/All [%d/%d/%d]",
			woutCount1, withCount1, allCount1,
			woutCount2, withCount2, allCount2),
	}
	lines = append(lines, missingDescPaths...)

	return osutil.CreateFileWithLines(filename, lines, "\n", true)
}

func (sm *SpecMore) SchemaPropertiesWithoutDescriptionsWriteFile(filename string) error {
	descStatus := sm.SchemaPropertiesDescriptionStatus()
	missingDescPaths := descStatus.Flatten("#/components/schemas", "/",
		maputil.MapStringMapStringIntFuncExactMatch(DescStatusIsEmpty),
		true, true)
	withCount1, withCount2 := descStatus.CountsWithVal(DescStatusIsNotEmpty, DescStatusDefaultSep)
	woutCount1, woutCount2 := descStatus.CountsWithVal(DescStatusIsEmpty, DescStatusDefaultSep)
	allCount1, allCount2 := descStatus.Counts(DescStatusDefaultSep)
	lines := []string{
		fmt.Sprintf("Schemas Missing/Have/All [%d/%d/%d] Props Missing/Have/All [%d/%d/%d]",
			woutCount1, withCount1, allCount1,
			woutCount2, withCount2, allCount2),
	}
	lines = append(lines, missingDescPaths...)

	return osutil.CreateFileWithLines(filename, lines, "\n", true)
}
<|endoftext|>


<commit_before>package web

import (
	"github.com/gorilla/mux"
	"github.com/jimmysawczuk/power-monitor/monitor"

	"bytes"
	"encoding/json"
	"html/template"
	"net/http"
	"path"
	"strconv"
	"time"
)

var activeMonitor *monitor.Monitor
var startTime time.Time
var indexTmpl *template.Template
var releaseMode = releaseModeDebug

const (
	releaseModeRelease = "release"
	releaseModeDebug   = "debug"
)

func init() {
	if releaseMode == releaseModeRelease {
		indexTmpl = template.Must(template.New("name").Parse(string(MustAsset("web/templates/index.html"))))
	}
}

func GetRouter(m *monitor.Monitor) *mux.Router {
	activeMonitor = m
	startTime = time.Now()

	r := mux.NewRouter()
	r.HandleFunc("/", getIndex)
	r.HandleFunc("/api/snapshots", getSnapshots)
	r.PathPrefix("/").HandlerFunc(getStaticFile)
	return r
}
func getStaticFile(w http.ResponseWriter, r *http.Request) {
	by, err := Asset("web/static" + r.URL.Path)
	if err != nil {
		w.WriteHeader(404)
		return
	}

	switch path.Ext(r.URL.Path) {
	case ".css":
		w.Header().Set("Content-Type", "text/css")
	case ".js":
		w.Header().Set("Content-Type", "application/javascript")
	}

	w.WriteHeader(200)
	w.Write(by)
}

func getIndex(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(200)

	tmpl := indexTmpl
	if tmpl == nil {
		tmpl = template.Must(template.New("name").Parse(string(MustAsset("web/templates/index.html"))))
	}

	var revision string
	if rev, err := Asset("web/static/REVISION.json"); err != nil {
		revision = "{}"
	} else {
		buf := &bytes.Buffer{}
		json.Compact(buf, rev)
		revision = buf.String()
	}

	tmpl.Execute(w, map[string]interface{}{
		"StartTime": startTime,
		"Interval":  int64(activeMonitor.Interval / 1e6),
		"Mode":      releaseMode,
		"Revision":  revision,
	})
}

func getSnapshots(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()

	now := time.Now()

	recent := activeMonitor.GetRecentSnapshots().Filter(func(s monitor.Snapshot) bool {

		switch {
		case isTimestampInLast(startTime, now, 3*time.Minute):
			return true

		case isTimestampInLast(startTime, now, 10*time.Minute):
			return isSignificantTimestamp(s.Timestamp, 10*time.Second)

		case isTimestampInLast(startTime, now, 1*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 30*time.Second)

		case isTimestampInLast(startTime, now, 6*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 5*time.Minute)

		case isTimestampInLast(startTime, now, 2*24*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 30*time.Minute)

		case isTimestampInLast(startTime, now, 4*24*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 1*time.Hour)

		case isTimestampInLast(s.Timestamp, now, 7*24*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 3*time.Hour)

		default:
			return false
		}
	})

	limit_str := r.FormValue("limit")
	limit, _ := strconv.ParseInt(limit_str, 10, 64)
	if limit > 0 && limit < int64(len(recent)) {
		recent = recent[0:limit]
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	by, _ := json.Marshal(recent)
	w.Write(by)
}

func isTimestampInLast(s, now time.Time, dur time.Duration) bool {
	return now.Sub(s) < dur
}

func isSignificantTimestamp(s time.Time, frequency time.Duration) bool {
	return (s.UnixNano()-startTime.UnixNano())%int64(frequency) < int64(activeMonitor.Interval)
}
<commit_msg>I think this is better...<commit_after>package web

import (
	"github.com/gorilla/mux"
	"github.com/jimmysawczuk/power-monitor/monitor"

	"bytes"
	"encoding/json"
	"html/template"
	"net/http"
	"path"
	"strconv"
	"time"
)

var activeMonitor *monitor.Monitor
var startTime time.Time
var indexTmpl *template.Template
var releaseMode = releaseModeDebug

const (
	releaseModeRelease = "release"
	releaseModeDebug   = "debug"
)

func init() {
	if releaseMode == releaseModeRelease {
		indexTmpl = template.Must(template.New("name").Parse(string(MustAsset("web/templates/index.html"))))
	}
}

func GetRouter(m *monitor.Monitor) *mux.Router {
	activeMonitor = m
	startTime = time.Now()

	r := mux.NewRouter()
	r.HandleFunc("/", getIndex)
	r.HandleFunc("/api/snapshots", getSnapshots)
	r.PathPrefix("/").HandlerFunc(getStaticFile)
	return r
}
func getStaticFile(w http.ResponseWriter, r *http.Request) {
	by, err := Asset("web/static" + r.URL.Path)
	if err != nil {
		w.WriteHeader(404)
		return
	}

	switch path.Ext(r.URL.Path) {
	case ".css":
		w.Header().Set("Content-Type", "text/css")
	case ".js":
		w.Header().Set("Content-Type", "application/javascript")
	}

	w.WriteHeader(200)
	w.Write(by)
}

func getIndex(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(200)

	tmpl := indexTmpl
	if tmpl == nil {
		tmpl = template.Must(template.New("name").Parse(string(MustAsset("web/templates/index.html"))))
	}

	var revision string
	if rev, err := Asset("web/static/REVISION.json"); err != nil {
		revision = "{}"
	} else {
		buf := &bytes.Buffer{}
		json.Compact(buf, rev)
		revision = buf.String()
	}

	tmpl.Execute(w, map[string]interface{}{
		"StartTime": startTime,
		"Interval":  int64(activeMonitor.Interval / 1e6),
		"Mode":      releaseMode,
		"Revision":  revision,
	})
}

func getSnapshots(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()

	now := time.Now()

	recent := activeMonitor.GetRecentSnapshots().Filter(func(s monitor.Snapshot) bool {

		switch {
		case isTimestampInLast(startTime, now, 3*time.Minute):
			return true

		case isTimestampInLast(startTime, now, 10*time.Minute):
			return isSignificantTimestamp(s.Timestamp, 10*time.Second)

		case isTimestampInLast(startTime, now, 1*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 30*time.Second)

		case isTimestampInLast(startTime, now, 6*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 5*time.Minute)

		case isTimestampInLast(startTime, now, 2*24*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 30*time.Minute)

		case isTimestampInLast(startTime, now, 4*24*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 1*time.Hour)

		case isTimestampInLast(s.Timestamp, now, 7*24*time.Hour):
			return isSignificantTimestamp(s.Timestamp, 3*time.Hour)

		default:
			return false
		}
	})

	limit_str := r.FormValue("limit")
	limit, _ := strconv.ParseInt(limit_str, 10, 64)
	if limit > 0 && limit < int64(len(recent)) {
		recent = recent[0:limit]
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(200)
	by, _ := json.Marshal(recent)
	w.Write(by)
}

func isTimestampInLast(s, now time.Time, dur time.Duration) bool {
	return now.Sub(s) < dur
}

func isSignificantTimestamp(s time.Time, frequency time.Duration) bool {
	return (s.UnixNano()-time.Now().UnixNano())%int64(frequency) < int64(activeMonitor.Interval)
}
<|endoftext|>


<commit_before>package main

import . "github.com/strickyak/basic_basic"
import "github.com/strickyak/basic_basic/draw"

import (
	"bufio"
	"bytes"
	"flag"
	. "fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

var Tmpl *template.Template

var THRU_CRUNCH = regexp.MustCompile("(?s).*\n#[# ]+\n(.*)")
var NON_ALPHANUM = regexp.MustCompile("[^A-Za-z0-9_]")

func encodeChar(s string) string {
	w := bytes.NewBuffer(nil)
	r := strings.NewReader(s)
	for r.Len() > 0 {
		ch, _, err := r.ReadRune()
		if err != nil {
			panic(err)
		}
		Fprintf(w, "{%d}", ch)
	}
	return w.String()
}
func CurlyEncode(s string) string {
	if s == "" {
		return "{}" // Special case for encoding the empty string.
	}
	return NON_ALPHANUM.ReplaceAllStringFunc(s, encodeChar)
}

func main() {
	flag.BoolVar(&Debug, "d", false, "debug bit")
	flag.Parse()

	Tmpl = template.New("basic-web")
	Tmpl.Parse(TEMPLATES)

	http.HandleFunc("/", handler)
	log.Println("http.ListenAndServe.")
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func handler(w http.ResponseWriter, req *http.Request) {
	defer func() {
		r := recover()
		if r != nil {
			w.Header().Set("Content-Type", "text/plain")
			Fprintf(w, "%v", r)
		}
	}()

	req.ParseForm()
	if f4, ok4 := req.Form["list"]; ok4 {
		what := f4[0]
		if what == "" {
			// List all files
			names, err := filepath.Glob("*.bas")
			if err != nil {
				panic(err)
			}
			sort.Strings(names)
			w.Header().Set("Content-Type", "text/html")
			Fprintf(w, "")
			for _, name := range names {
				Fprintf(w, `<a href="/?list=%s">%s</a><br>`+"\n", name, name)
			}
		} else {
			// List one file
			fd, err := os.Open(what)
			if err != nil {
				panic(err)
			}
			w.Header().Set("Content-Type", "text/plain")
			_, err = io.Copy(w, fd)
			if err != nil {
				panic(err)
			}
			err = fd.Close()
			if err != nil {
				panic(err)
			}
		}
	} else if f1, ok1 := req.Form["run"]; ok1 {
		var putchar func(ch byte)
		forward_putchar := func(ch byte) {
			putchar(ch)
		}

		terp := NewTerp(f1[0], forward_putchar)
		terp.SetExpiration("30s")
		d := draw.Register(terp)
		putchar = d.Putchar

		if f3, ok3 := req.Form["progname"]; ok3 {
			name := f3[0]
			if name == "" {
				name = "untitled"
			}
			name = strings.Trim(name, " \t\r\n")
			flags := os.O_CREATE | os.O_WRONLY | os.O_APPEND
			if strings.HasSuffix(name, "!") {
				flags |= os.O_EXCL
			}
			fd, err := os.OpenFile(CurlyEncode(name)+".bas", flags, 0666)
			if err != nil {
				panic(err)
			}
			w := bufio.NewWriter(fd)
			Fprintf(w, "###### ###### ###### ###### ###### ######\n")
			Fprintf(w, "%s\n", strings.Replace(f1[0], "\r", "", -1))
			w.Flush()
			fd.Close()
		}

		terp.Run()
		if d.HasImage() {
			w.Header().Set("Content-Type", "image/png")
			d.WritePng(w)
		} else {
			w.Header().Set("Content-Type", "text/plain")
			Fprintf(w, "Use 'PRINT' or 'CALL triangle' statements to produce output.")
		}
	} else {
		dict := make(map[string]interface{})
		if f2, ok2 := req.Form["load"]; ok2 {
			code, err := ioutil.ReadFile(strings.Trim(f2[0], " \t\n\r"))
			s := "\n" + string(code)
			if err != nil {
				panic(err)
			}
			m := THRU_CRUNCH.FindStringSubmatch(s)
			if m != nil {
				s = m[1]
			}
			dict["Code"] = s
		} else {
			dict["Code"] = template.HTML(DEMO)
		}

		{
			names, err := filepath.Glob("*{33}.bas")
			if err != nil {
				panic(err)
			}
			for i, e := range names {
				names[i] = e[:len(e)-8] // Chop "{33}.bas"
			}
			sort.Strings(names)
			dict["Links"] = names
		}

		Tmpl.ExecuteTemplate(w, "Main", dict)
	}
}

const TEMPLATES = `
{{define "Main"}}

      <form method="POST" action="/">
        <textarea name=run cols=80 rows=25>{{$.Code}}</textarea>

        <input type=submit name=submit value=Submit>

        ( Save as: <input type=text width=20 name=progname> )
      </form>

    Demos: {{range .Links}} {{.}}   {{end}}

      {{template "Doc" $}}
{{end}}

{{define "Doc"}}

See saved programs.

This is a simple BASIC computer.

The only data type is floating point numbers.

THe only output is the "CALL triangle" statement,
which draws colored triangles on a canvas with
coordinates [0 .. 100) on both x and y axes.

Statement ::= LineNumber Stmt
Stmt ::= REM remark...
       | DIM arr(size), matrix(width,heigth)
       | LET var := expr
       | LET arr(i, j...) := expr
       | GOTO n
       | IF expr THEN y
       | IF expr THEN y ELSE n
       | FOR var = a TO b
       | NEXT var
       | GOSUB n
       | RETURN
       | PRINT expr
       | PRINT strlit
       | CALL triangle( x1, y1, x2, y2, x3, y3, rgb )
       | STOP
       | END
    ... where n & y are line numbers
    ... where rgb is decimal (r=hundreds, g=tens, b=ones)
expr ::= sum relop expr     ...where relop can be == != < > <= >=
sum ::= prod addop sum      ...where addop can be + -
prod ::= composite mulop prod    ...where mulop can be * / %%
composite ::= prim
            | arr(i, j...)
prim ::= number
       | var
       | ( expr )
strlit ::= "AnyASCIIButDoubleADoubleQuote"

{{end}}
`

const DEMO = `
1  REM Draw big grey triangle, then 1000 smaller colored ones.
5  CALL triangle( 0,0, 0,99, 99,0, 444 )
10 for i = 0 to 9
20   for j = 0 to 9
30     for k = 0 to 9
50       call triangle (i*10,k+j*10,  9+i*10,j*10,  9+i*10,9+j*10, i+j*10+(9-k)*100)
70     next k
80   next j
90 next i
`
<commit_msg>Better Demo insruction.<commit_after>package main

import . "github.com/strickyak/basic_basic"
import "github.com/strickyak/basic_basic/draw"

import (
	"bufio"
	"bytes"
	"flag"
	. "fmt"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
)

var Tmpl *template.Template

var THRU_CRUNCH = regexp.MustCompile("(?s).*\n#[# ]+\n(.*)")
var NON_ALPHANUM = regexp.MustCompile("[^A-Za-z0-9_]")

func encodeChar(s string) string {
	w := bytes.NewBuffer(nil)
	r := strings.NewReader(s)
	for r.Len() > 0 {
		ch, _, err := r.ReadRune()
		if err != nil {
			panic(err)
		}
		Fprintf(w, "{%d}", ch)
	}
	return w.String()
}
func CurlyEncode(s string) string {
	if s == "" {
		return "{}" // Special case for encoding the empty string.
	}
	return NON_ALPHANUM.ReplaceAllStringFunc(s, encodeChar)
}

func main() {
	flag.BoolVar(&Debug, "d", false, "debug bit")
	flag.Parse()

	Tmpl = template.New("basic-web")
	Tmpl.Parse(TEMPLATES)

	http.HandleFunc("/", handler)
	log.Println("http.ListenAndServe.")
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func handler(w http.ResponseWriter, req *http.Request) {
	defer func() {
		r := recover()
		if r != nil {
			w.Header().Set("Content-Type", "text/plain")
			Fprintf(w, "%v", r)
		}
	}()

	req.ParseForm()
	if f4, ok4 := req.Form["list"]; ok4 {
		what := f4[0]
		if what == "" {
			// List all files
			names, err := filepath.Glob("*.bas")
			if err != nil {
				panic(err)
			}
			sort.Strings(names)
			w.Header().Set("Content-Type", "text/html")
			Fprintf(w, "")
			for _, name := range names {
				Fprintf(w, `<a href="/?list=%s">%s</a><br>`+"\n", name, name)
			}
		} else {
			// List one file
			fd, err := os.Open(what)
			if err != nil {
				panic(err)
			}
			w.Header().Set("Content-Type", "text/plain")
			_, err = io.Copy(w, fd)
			if err != nil {
				panic(err)
			}
			err = fd.Close()
			if err != nil {
				panic(err)
			}
		}
	} else if f1, ok1 := req.Form["run"]; ok1 {
		var putchar func(ch byte)
		forward_putchar := func(ch byte) {
			putchar(ch)
		}

		terp := NewTerp(f1[0], forward_putchar)
		terp.SetExpiration("30s")
		d := draw.Register(terp)
		putchar = d.Putchar

		if f3, ok3 := req.Form["progname"]; ok3 {
			name := f3[0]
			if name == "" {
				name = "untitled"
			}
			name = strings.Trim(name, " \t\r\n")
			flags := os.O_CREATE | os.O_WRONLY | os.O_APPEND
			if strings.HasSuffix(name, "!") {
				flags |= os.O_EXCL
			}
			fd, err := os.OpenFile(CurlyEncode(name)+".bas", flags, 0666)
			if err != nil {
				panic(err)
			}
			w := bufio.NewWriter(fd)
			Fprintf(w, "###### ###### ###### ###### ###### ######\n")
			Fprintf(w, "%s\n", strings.Replace(f1[0], "\r", "", -1))
			w.Flush()
			fd.Close()
		}

		terp.Run()
		if d.HasImage() {
			w.Header().Set("Content-Type", "image/png")
			d.WritePng(w)
		} else {
			w.Header().Set("Content-Type", "text/plain")
			Fprintf(w, "Use 'PRINT' or 'CALL triangle' statements to produce output.")
		}
	} else {
		dict := make(map[string]interface{})
		if f2, ok2 := req.Form["load"]; ok2 {
			code, err := ioutil.ReadFile(strings.Trim(f2[0], " \t\n\r"))
			s := "\n" + string(code)
			if err != nil {
				panic(err)
			}
			m := THRU_CRUNCH.FindStringSubmatch(s)
			if m != nil {
				s = m[1]
			}
			dict["Code"] = s
		} else {
			dict["Code"] = template.HTML(DEMO)
		}

		{
			names, err := filepath.Glob("*{33}.bas")
			if err != nil {
				panic(err)
			}
			for i, e := range names {
				names[i] = e[:len(e)-8] // Chop "{33}.bas"
			}
			sort.Strings(names)
			dict["Links"] = names
		}

		Tmpl.ExecuteTemplate(w, "Main", dict)
	}
}

const TEMPLATES = `
{{define "Main"}}

      <form method="POST" action="/">
        <textarea name=run cols=80 rows=25>{{$.Code}}</textarea>

        <input type=submit name=submit value=Submit>

        ( Save as: <input type=text width=20 name=progname> )
      </form>

    Demos:
    {{range .Links}} {{.}}   {{end}}
      (then click Submit)

      {{template "Doc" $}}
{{end}}

{{define "Doc"}}

See saved programs.

This is a simple BASIC computer.

The only data type is floating point numbers.

THe only output is the "CALL triangle" statement,
which draws colored triangles on a canvas with
coordinates [0 .. 100) on both x and y axes.

Statement ::= LineNumber Stmt
Stmt ::= REM remark...
       | DIM arr(size), matrix(width,heigth)
       | LET var := expr
       | LET arr(i, j...) := expr
       | GOTO n
       | IF expr THEN y
       | IF expr THEN y ELSE n
       | FOR var = a TO b
       | NEXT var
       | GOSUB n
       | RETURN
       | PRINT expr
       | PRINT strlit
       | CALL triangle( x1, y1, x2, y2, x3, y3, rgb )
       | STOP
       | END
    ... where n & y are line numbers
    ... where rgb is decimal (r=hundreds, g=tens, b=ones)
expr ::= sum relop expr     ...where relop can be == != < > <= >=
sum ::= prod addop sum      ...where addop can be + -
prod ::= composite mulop prod    ...where mulop can be * / %%
composite ::= prim
            | arr(i, j...)
prim ::= number
       | var
       | ( expr )
strlit ::= "AnyASCIIButDoubleADoubleQuote"

{{end}}
`

const DEMO = `
1  REM Draw big grey triangle, then 1000 smaller colored ones.
5  CALL triangle( 0,0, 0,99, 99,0, 444 )
10 for i = 0 to 9
20   for j = 0 to 9
30     for k = 0 to 9
50       call triangle (i*10,k+j*10,  9+i*10,j*10,  9+i*10,9+j*10, i+j*10+(9-k)*100)
70     next k
80   next j
90 next i
`
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"net/http"

	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
)

var (
	ip            = flag.String("ip", "", "ip the webhook should serve hooks on")
	port          = flag.Int("port", 9000, "port the webhook should serve hooks on")
	verbose       = flag.Bool("verbose", false, "show verbose output")
	hooksFilePath = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve")
)

func init() {
	flag.Parse()

	// load and parse hooks

	// set up file watcher
}

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/hooks/{id}", hookHandler)

	n := negroni.Classic()
	n.UseHandler(router)

	n.Run(fmt.Sprintf("%s:%d", *ip, *port))
}

func hookHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]

	// parse headers

	// parse body

	// find hook

	// trigger hook

	// say thanks

	fmt.Fprintf(w, "Thanks. %s %+v %+v %+v", id, vars, r.Header, r.Body)
}
<commit_msg>added logger<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
)

var (
	ip            = flag.String("ip", "", "ip the webhook should serve hooks on")
	port          = flag.Int("port", 9000, "port the webhook should serve hooks on")
	verbose       = flag.Bool("verbose", false, "show verbose output")
	hooksFilePath = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve")
)

func init() {
	flag.Parse()

	log.SetPrefix("[webhook] ")
	log.SetFlags(log.Ldate | log.Ltime)

	if !*verbose {
		log.SetOutput(ioutil.Discard)
	}

	log.Println("starting")

	// load and parse hooks
	log.Printf("attempting to load hooks from %s\n", *hooksFilePath)

	// set up file watcher
	log.Printf("setting up file watcher for %s\n", *hooksFilePath)
}

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/hooks/{id}", hookHandler)

	n := negroni.Classic()
	n.UseHandler(router)

	n.Run(fmt.Sprintf("%s:%d", *ip, *port))
}

func hookHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id := vars["id"]

	// parse headers

	// parse body

	// find hook

	// trigger hook

	// say thanks

	fmt.Fprintf(w, "Thanks. %s %+v %+v %+v", id, vars, r.Header, r.Body)
}
<|endoftext|>


<commit_before>package acr

import (
	"bufio"
	"context"
	cr "github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2018-09-01/containerregistry"
	"github.com/Azure/go-autorest/autorest/azure/auth"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"io"
	"net/http"
	"regexp"
	"time"
)

const BUILD_STATUS_HEADER = "x-ms-meta-Complete"

func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]build.Artifact, error) {
	return build.InParallel(ctx, out, tagger, artifacts, b.buildArtifact)
}

func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *latest.Artifact) (string, error) {
	client := cr.NewRegistriesClient(b.Credentials.SubscriptionId)
	authorizer, err := auth.NewClientCredentialsConfig(b.Credentials.ClientId, b.Credentials.ClientSecret, b.Credentials.TenantId).Authorizer()
	if err != nil {
		return "", errors.Wrap(err, "authorizing client")
	}
	client.Authorizer = authorizer

	result, err := client.GetBuildSourceUploadURL(ctx, b.ResourceGroup, b.ContainerRegistry)
	if err != nil {
		return "", errors.Wrap(err, "build source upload url")
	}
	blob := NewBlobStorage(*result.UploadURL)

	err = docker.CreateDockerTarGzContext(blob.Writer(), artifact.Workspace, artifact.DockerArtifact)
	if err != nil {
		return "", errors.Wrap(err, "create context tar.gz")
	}

	err = blob.UploadFileToBlob()
	if err != nil {
		return "", errors.Wrap(err, "upload file to blob")
	}

	imageTag, err := tagger.GenerateFullyQualifiedImageName(artifact.Workspace, &tag.Options{
		Digest:    util.RandomID(),
		ImageName: artifact.ImageName,
	})
	if err != nil {
		return "", errors.Wrap(err, "create fully qualified image name")
	}

	imageTag, err = getImageTagWithoutFQDN(imageTag)
	if err != nil {
		return "", errors.Wrap(err, "get azure image tag")
	}

	buildRequest := cr.DockerBuildRequest{
		ImageNames:     &[]string{imageTag},
		IsPushEnabled:  &[]bool{true}[0], //who invented bool pointers
		SourceLocation: result.RelativePath,
		Platform: &cr.PlatformProperties{
			Variant:      cr.V8,
			Os:           cr.Linux,
			Architecture: cr.Amd64,
		},
		DockerFilePath: &artifact.DockerArtifact.DockerfilePath,
		Type:           cr.TypeDockerBuildRequest,
	}
	future, err := client.ScheduleRun(ctx, b.ResourceGroup, b.ContainerRegistry, buildRequest)
	if err != nil {
		return "", errors.Wrap(err, "schedule build request")
	}

	run, err := future.Result(client)
	if err != nil {
		return "", errors.Wrap(err, "get run id")
	}
	runId := *run.RunID

	runsClient := cr.NewRunsClient(b.Credentials.SubscriptionId)
	runsClient.Authorizer = client.Authorizer
	logUrl, err := runsClient.GetLogSasURL(ctx, b.ResourceGroup, b.ContainerRegistry, runId)
	if err != nil {
		return "", errors.Wrap(err, "get log url")
	}

	err = pollBuildStatus(*logUrl.LogLink, out)
	if err != nil {
		return "", errors.Wrap(err, "polling build status")
	}

	return imageTag, nil
}

func pollBuildStatus(logUrl string, out io.Writer) error {
	offset := int32(0)
	for {
		resp, err := http.Get(logUrl)
		if err != nil {
			return err
		}

		if resp.StatusCode == http.StatusNotFound {
			//if blob is not available yet, try again
			time.Sleep(2 * time.Second)
			continue
		}

		scanner := bufio.NewScanner(resp.Body)
		line := int32(0)
		for scanner.Scan() {
			if line > offset {
				out.Write(scanner.Bytes())
				line++
				offset++
			}
		}
		resp.Body.Close()

		switch resp.Header.Get(BUILD_STATUS_HEADER) {
		case "": //run succeeded when there is no status header
			return nil
		case "internalerror":
		case "failed":
			return errors.New("run failed")
		case "timedout":
			return errors.New("run timed out")
		case "canceled":
			return errors.New("run was canceled")
		}

		time.Sleep(2 * time.Second)
	}
}

// ACR needs the image tag in the following format
// <registryName>/<repository>:<tag>
func getImageTagWithoutFQDN(imageTag string) (string, error) {
	r, err := regexp.Compile("(.*)\\..*\\..*(/.*)")
	if err != nil {
		return "", errors.Wrap(err, "create regexp")
	}

	matches := r.FindStringSubmatch(imageTag)
	if len(matches) < 3 {
		return "", errors.New("invalid image tag")
	}

	return matches[1] + matches[2], nil
}
<commit_msg>Check if response has data before checking for completion<commit_after>package acr

import (
	"bufio"
	"context"
	cr "github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2018-09-01/containerregistry"
	"github.com/Azure/go-autorest/autorest/azure/auth"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/pkg/errors"
	"io"
	"net/http"
	"regexp"
	"time"
)

const BUILD_STATUS_HEADER = "x-ms-meta-Complete"

func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]build.Artifact, error) {
	return build.InParallel(ctx, out, tagger, artifacts, b.buildArtifact)
}

func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *latest.Artifact) (string, error) {
	client := cr.NewRegistriesClient(b.Credentials.SubscriptionId)
	authorizer, err := auth.NewClientCredentialsConfig(b.Credentials.ClientId, b.Credentials.ClientSecret, b.Credentials.TenantId).Authorizer()
	if err != nil {
		return "", errors.Wrap(err, "authorizing client")
	}
	client.Authorizer = authorizer

	result, err := client.GetBuildSourceUploadURL(ctx, b.ResourceGroup, b.ContainerRegistry)
	if err != nil {
		return "", errors.Wrap(err, "build source upload url")
	}
	blob := NewBlobStorage(*result.UploadURL)

	err = docker.CreateDockerTarGzContext(blob.Writer(), artifact.Workspace, artifact.DockerArtifact)
	if err != nil {
		return "", errors.Wrap(err, "create context tar.gz")
	}

	err = blob.UploadFileToBlob()
	if err != nil {
		return "", errors.Wrap(err, "upload file to blob")
	}

	imageTag, err := tagger.GenerateFullyQualifiedImageName(artifact.Workspace, &tag.Options{
		Digest:    util.RandomID(),
		ImageName: artifact.ImageName,
	})
	if err != nil {
		return "", errors.Wrap(err, "create fully qualified image name")
	}

	imageTag, err = getImageTagWithoutFQDN(imageTag)
	if err != nil {
		return "", errors.Wrap(err, "get azure image tag")
	}

	buildRequest := cr.DockerBuildRequest{
		ImageNames:     &[]string{imageTag},
		IsPushEnabled:  &[]bool{true}[0], //who invented bool pointers
		SourceLocation: result.RelativePath,
		Platform: &cr.PlatformProperties{
			Variant:      cr.V8,
			Os:           cr.Linux,
			Architecture: cr.Amd64,
		},
		DockerFilePath: &artifact.DockerArtifact.DockerfilePath,
		Type:           cr.TypeDockerBuildRequest,
	}
	future, err := client.ScheduleRun(ctx, b.ResourceGroup, b.ContainerRegistry, buildRequest)
	if err != nil {
		return "", errors.Wrap(err, "schedule build request")
	}

	run, err := future.Result(client)
	if err != nil {
		return "", errors.Wrap(err, "get run id")
	}
	runId := *run.RunID

	runsClient := cr.NewRunsClient(b.Credentials.SubscriptionId)
	runsClient.Authorizer = client.Authorizer
	logUrl, err := runsClient.GetLogSasURL(ctx, b.ResourceGroup, b.ContainerRegistry, runId)
	if err != nil {
		return "", errors.Wrap(err, "get log url")
	}

	err = pollBuildStatus(*logUrl.LogLink, out)
	if err != nil {
		return "", errors.Wrap(err, "polling build status")
	}

	return imageTag, nil
}

func pollBuildStatus(logUrl string, out io.Writer) error {
	offset := int32(0)
	for {
		resp, err := http.Get(logUrl)
		if err != nil {
			return err
		}

		if resp.StatusCode == http.StatusNotFound {
			//if blob is not available yet, try again
			time.Sleep(2 * time.Second)
			continue
		}

		scanner := bufio.NewScanner(resp.Body)
		line := int32(0)
		for scanner.Scan() {
			if line > offset {
				out.Write(scanner.Bytes())
				line++
				offset++
			}
		}
		resp.Body.Close()

		if offset > 0 {
			switch resp.Header.Get(BUILD_STATUS_HEADER) {
			case "": //run succeeded when there is no status header
				return nil
			case "internalerror":
			case "failed":
				return errors.New("run failed")
			case "timedout":
				return errors.New("run timed out")
			case "canceled":
				return errors.New("run was canceled")
			}
		}

		time.Sleep(2 * time.Second)
	}
}

// ACR needs the image tag in the following format
// <registryName>/<repository>:<tag>
func getImageTagWithoutFQDN(imageTag string) (string, error) {
	r, err := regexp.Compile("(.*)\\..*\\..*(/.*)")
	if err != nil {
		return "", errors.Wrap(err, "create regexp")
	}

	matches := r.FindStringSubmatch(imageTag)
	if len(matches) < 3 {
		return "", errors.New("invalid image tag")
	}

	return matches[1] + matches[2], nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 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 deploy

import (
	"context"
	"fmt"
	"io/ioutil"
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/GoogleContainerTools/skaffold/testutil"
	"github.com/pkg/errors"
)

const (
	testKubeContext = "kubecontext"
	kubectlVersion  = `{"clientVersion":{"major":"1","minor":"12"}}`
)

const deploymentWebYAML = `apiVersion: v1
kind: Pod
metadata:
  name: leeroy-web
spec:
  containers:
  - name: leeroy-web
    image: leeroy-web`

const deploymentAppYAML = `apiVersion: v1
kind: Pod
metadata:
  name: leeroy-app
spec:
  containers:
  - name: leeroy-app
    image: leeroy-app`

func TestKubectlDeploy(t *testing.T) {
	tests := []struct {
		description          string
		cfg                  *latest.KubectlDeploy
		builds               []build.Artifact
		command              util.Command
		shouldErr            bool
		forceDeploy          bool
		expectedDependencies []string
	}{
		{
			description: "no manifest",
			cfg:         &latest.KubectlDeploy{},
			command:     testutil.FakeRunOut(t, "kubectl version --client -ojson", kubectlVersion),
		},
		{
			description: "missing manifest file",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"missing.yaml"},
			},
			command: testutil.FakeRunOut(t, "kubectl version --client -ojson", kubectlVersion),
		},
		{
			description: "ignore non-manifest",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"*.ignored"},
			},
			command: testutil.FakeRunOut(t, "kubectl version --client -ojson", kubectlVersion),
		},
		{
			description: "deploy success (forced)",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace apply -f - --force"),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			forceDeploy:          true,
			expectedDependencies: []string{"deployment.yaml"},
		},
		{
			description: "deploy success",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace apply -f -"),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			expectedDependencies: []string{"deployment.yaml"},
		},
		{
			description: "http manifest",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml", "http://remote.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml -f http://remote.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace apply -f -"),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			expectedDependencies: []string{"deployment.yaml"},
		},
		{
			description: "deploy command error",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRunErr("kubectl --context kubecontext --namespace testNamespace apply -f -", fmt.Errorf("")),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			shouldErr:            true,
			expectedDependencies: []string{"deployment.yaml"},
		},
		{
			description: "additional flags",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
				Flags: latest.KubectlFlags{
					Global: []string{"-v=0"},
					Apply:  []string{"--overwrite=true"},
					Delete: []string{"ignored"},
				},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create -v=0 --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRunErr("kubectl --context kubecontext --namespace testNamespace apply -v=0 --overwrite=true -f -", fmt.Errorf("")),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			shouldErr:            true,
			expectedDependencies: []string{"deployment.yaml"},
		},
	}
	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			t.Override(&util.DefaultExecCommand, test.command)
			t.NewTempDir().
				Write("deployment.yaml", deploymentWebYAML).
				Touch("empty.ignored").
				Chdir()

			k := NewKubectlDeployer(&runcontext.RunContext{
				WorkingDir: ".",
				Cfg: latest.Pipeline{
					Deploy: latest.DeployConfig{
						DeployType: latest.DeployType{
							KubectlDeploy: test.cfg,
						},
					},
				},
				KubeContext: testKubeContext,
				Opts: config.SkaffoldOptions{
					Namespace: testNamespace,
					Force:     test.forceDeploy,
				},
			})

			dependencies, err := k.Dependencies()
			t.CheckNoError(err)
			t.CheckDeepEqual(test.expectedDependencies, dependencies)

			err = k.Deploy(context.Background(), ioutil.Discard, test.builds, nil)
			t.CheckError(test.shouldErr, err)
		})
	}
}

func TestKubectlCleanup(t *testing.T) {
	tests := []struct {
		description string
		cfg         *latest.KubectlDeploy
		command     util.Command
		shouldErr   bool
	}{
		{
			description: "cleanup success",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true -f -"),
		},
		{
			description: "cleanup error",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRunErr("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true -f -", errors.New("BUG")),
			shouldErr: true,
		},
		{
			description: "additional flags",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
				Flags: latest.KubectlFlags{
					Global: []string{"-v=0"},
					Apply:  []string{"ignored"},
					Delete: []string{"--grace-period=1"},
				},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create -v=0 --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace delete -v=0 --grace-period=1 --ignore-not-found=true -f -"),
		},
	}
	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			t.Override(&util.DefaultExecCommand, test.command)
			t.NewTempDir().
				Write("deployment.yaml", deploymentWebYAML).
				Chdir()

			k := NewKubectlDeployer(&runcontext.RunContext{
				WorkingDir: ".",
				Cfg: latest.Pipeline{
					Deploy: latest.DeployConfig{
						DeployType: latest.DeployType{
							KubectlDeploy: test.cfg,
						},
					},
				},
				KubeContext: testKubeContext,
				Opts: config.SkaffoldOptions{
					Namespace: testNamespace,
				},
			})
			err := k.Cleanup(context.Background(), ioutil.Discard)

			t.CheckError(test.shouldErr, err)
		})
	}
}

func TestKubectlRedeploy(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		tmpDir := t.NewTempDir().
			Write("deployment-web.yaml", deploymentWebYAML).
			Write("deployment-app.yaml", deploymentAppYAML)

		t.Override(&util.DefaultExecCommand, t.
			FakeRunOut("kubectl version --client -ojson", kubectlVersion).
			WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML).
			WithRunInput("kubectl --context kubecontext --namespace testNamespace apply -f -", `apiVersion: v1
kind: Pod
metadata:
  labels:
    skaffold.dev/deployer: kubectl
  name: leeroy-app
spec:
  containers:
  - image: leeroy-app:v1
    name: leeroy-app
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    skaffold.dev/deployer: kubectl
  name: leeroy-web
spec:
  containers:
  - image: leeroy-web:v1
    name: leeroy-web`).
			WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML).
			WithRunInput("kubectl --context kubecontext --namespace testNamespace apply -f -", `apiVersion: v1
kind: Pod
metadata:
  labels:
    skaffold.dev/deployer: kubectl
  name: leeroy-app
spec:
  containers:
  - image: leeroy-app:v2
    name: leeroy-app`).
			WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML),
		)

		cfg := &latest.KubectlDeploy{
			Manifests: []string{tmpDir.Path("deployment-app.yaml"), "deployment-web.yaml"},
		}
		deployer := NewKubectlDeployer(&runcontext.RunContext{
			WorkingDir: tmpDir.Root(),
			Cfg: latest.Pipeline{
				Deploy: latest.DeployConfig{
					DeployType: latest.DeployType{
						KubectlDeploy: cfg,
					},
				},
			},
			KubeContext: testKubeContext,
			Opts: config.SkaffoldOptions{
				Namespace: testNamespace,
			},
		})
		labellers := []Labeller{deployer}

		// Deploy one manifest
		err := deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{
			{ImageName: "leeroy-web", Tag: "leeroy-web:v1"},
			{ImageName: "leeroy-app", Tag: "leeroy-app:v1"},
		}, labellers)
		t.CheckNoError(err)

		// Deploy one manifest since only one image is updated
		err = deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{
			{ImageName: "leeroy-web", Tag: "leeroy-web:v1"},
			{ImageName: "leeroy-app", Tag: "leeroy-app:v2"},
		}, labellers)
		t.CheckNoError(err)

		// Deploy zero manifest since no image is updated
		err = deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{
			{ImageName: "leeroy-web", Tag: "leeroy-web:v1"},
			{ImageName: "leeroy-app", Tag: "leeroy-app:v2"},
		}, labellers)
		t.CheckNoError(err)
	})
}
<commit_msg>Add test for remote manifest<commit_after>/*
Copyright 2019 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 deploy

import (
	"context"
	"fmt"
	"io/ioutil"
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/GoogleContainerTools/skaffold/testutil"
	"github.com/pkg/errors"
)

const (
	testKubeContext = "kubecontext"
	kubectlVersion  = `{"clientVersion":{"major":"1","minor":"12"}}`
)

const deploymentWebYAML = `apiVersion: v1
kind: Pod
metadata:
  name: leeroy-web
spec:
  containers:
  - name: leeroy-web
    image: leeroy-web`

const deploymentAppYAML = `apiVersion: v1
kind: Pod
metadata:
  name: leeroy-app
spec:
  containers:
  - name: leeroy-app
    image: leeroy-app`

func TestKubectlDeploy(t *testing.T) {
	tests := []struct {
		description          string
		cfg                  *latest.KubectlDeploy
		builds               []build.Artifact
		command              util.Command
		shouldErr            bool
		forceDeploy          bool
		expectedDependencies []string
	}{
		{
			description: "no manifest",
			cfg:         &latest.KubectlDeploy{},
			command:     testutil.FakeRunOut(t, "kubectl version --client -ojson", kubectlVersion),
		},
		{
			description: "missing manifest file",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"missing.yaml"},
			},
			command: testutil.FakeRunOut(t, "kubectl version --client -ojson", kubectlVersion),
		},
		{
			description: "ignore non-manifest",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"*.ignored"},
			},
			command: testutil.FakeRunOut(t, "kubectl version --client -ojson", kubectlVersion),
		},
		{
			description: "deploy success (forced)",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace apply -f - --force"),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			forceDeploy:          true,
			expectedDependencies: []string{"deployment.yaml"},
		},
		{
			description: "deploy success",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace apply -f -"),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			expectedDependencies: []string{"deployment.yaml"},
		},
		{
			description: "http manifest",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml", "http://remote.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml -f http://remote.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace apply -f -"),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			expectedDependencies: []string{"deployment.yaml"},
		},
		{
			description: "deploy command error",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRunErr("kubectl --context kubecontext --namespace testNamespace apply -f -", fmt.Errorf("")),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			shouldErr:            true,
			expectedDependencies: []string{"deployment.yaml"},
		},
		{
			description: "additional flags",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
				Flags: latest.KubectlFlags{
					Global: []string{"-v=0"},
					Apply:  []string{"--overwrite=true"},
					Delete: []string{"ignored"},
				},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl version --client -ojson", kubectlVersion).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create -v=0 --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRunErr("kubectl --context kubecontext --namespace testNamespace apply -v=0 --overwrite=true -f -", fmt.Errorf("")),
			builds: []build.Artifact{{
				ImageName: "leeroy-web",
				Tag:       "leeroy-web:123",
			}},
			shouldErr:            true,
			expectedDependencies: []string{"deployment.yaml"},
		},
	}
	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			t.Override(&util.DefaultExecCommand, test.command)
			t.NewTempDir().
				Write("deployment.yaml", deploymentWebYAML).
				Touch("empty.ignored").
				Chdir()

			k := NewKubectlDeployer(&runcontext.RunContext{
				WorkingDir: ".",
				Cfg: latest.Pipeline{
					Deploy: latest.DeployConfig{
						DeployType: latest.DeployType{
							KubectlDeploy: test.cfg,
						},
					},
				},
				KubeContext: testKubeContext,
				Opts: config.SkaffoldOptions{
					Namespace: testNamespace,
					Force:     test.forceDeploy,
				},
			})

			dependencies, err := k.Dependencies()
			t.CheckNoError(err)
			t.CheckDeepEqual(test.expectedDependencies, dependencies)

			err = k.Deploy(context.Background(), ioutil.Discard, test.builds, nil)
			t.CheckError(test.shouldErr, err)
		})
	}
}

func TestKubectlCleanup(t *testing.T) {
	tests := []struct {
		description string
		cfg         *latest.KubectlDeploy
		command     util.Command
		shouldErr   bool
	}{
		{
			description: "cleanup success",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true -f -"),
		},
		{
			description: "cleanup error",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRunErr("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true -f -", errors.New("BUG")),
			shouldErr: true,
		},
		{
			description: "additional flags",
			cfg: &latest.KubectlDeploy{
				Manifests: []string{"deployment.yaml"},
				Flags: latest.KubectlFlags{
					Global: []string{"-v=0"},
					Apply:  []string{"ignored"},
					Delete: []string{"--grace-period=1"},
				},
			},
			command: testutil.NewFakeCmd(t).
				WithRunOut("kubectl --context kubecontext --namespace testNamespace create -v=0 --dry-run -oyaml -f deployment.yaml", deploymentWebYAML).
				WithRun("kubectl --context kubecontext --namespace testNamespace delete -v=0 --grace-period=1 --ignore-not-found=true -f -"),
		},
	}
	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			t.Override(&util.DefaultExecCommand, test.command)
			t.NewTempDir().
				Write("deployment.yaml", deploymentWebYAML).
				Chdir()

			k := NewKubectlDeployer(&runcontext.RunContext{
				WorkingDir: ".",
				Cfg: latest.Pipeline{
					Deploy: latest.DeployConfig{
						DeployType: latest.DeployType{
							KubectlDeploy: test.cfg,
						},
					},
				},
				KubeContext: testKubeContext,
				Opts: config.SkaffoldOptions{
					Namespace: testNamespace,
				},
			})
			err := k.Cleanup(context.Background(), ioutil.Discard)

			t.CheckError(test.shouldErr, err)
		})
	}
}

func TestKubectlDeployerRemoteCleanup(t *testing.T) {
	cfg := &latest.KubectlDeploy{
		RemoteManifests: []string{"pod/leeroy-web"},
	}

	testutil.Run(t, "cleanup remote", func(t *testutil.T) {
		command := t.FakeRun("kubectl --context kubecontext --namespace testNamespace get pod/leeroy-web -o yaml").
			WithRun("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true -f -").
			WithRunInput("kubectl --context kubecontext --namespace testNamespace apply -f -", deploymentWebYAML)

		t.Override(&util.DefaultExecCommand, command)
		t.NewTempDir().
			Write("deployment.yaml", deploymentWebYAML).
			Chdir()

		k := NewKubectlDeployer(&runcontext.RunContext{
			WorkingDir: ".",
			Cfg: &latest.Pipeline{
				Deploy: latest.DeployConfig{
					DeployType: latest.DeployType{
						KubectlDeploy: cfg,
					},
				},
			},
			KubeContext: testKubeContext,
			Opts: &config.SkaffoldOptions{
				Namespace: testNamespace,
			},
		})
		err := k.Cleanup(context.Background(), ioutil.Discard)

		t.CheckError(false, err)
	})
}

func TestKubectlRedeploy(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		tmpDir := t.NewTempDir().
			Write("deployment-web.yaml", deploymentWebYAML).
			Write("deployment-app.yaml", deploymentAppYAML)

		t.Override(&util.DefaultExecCommand, t.
			FakeRunOut("kubectl version --client -ojson", kubectlVersion).
			WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML).
			WithRunInput("kubectl --context kubecontext --namespace testNamespace apply -f -", `apiVersion: v1
kind: Pod
metadata:
  labels:
    skaffold.dev/deployer: kubectl
  name: leeroy-app
spec:
  containers:
  - image: leeroy-app:v1
    name: leeroy-app
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    skaffold.dev/deployer: kubectl
  name: leeroy-web
spec:
  containers:
  - image: leeroy-web:v1
    name: leeroy-web`).
			WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML).
			WithRunInput("kubectl --context kubecontext --namespace testNamespace apply -f -", `apiVersion: v1
kind: Pod
metadata:
  labels:
    skaffold.dev/deployer: kubectl
  name: leeroy-app
spec:
  containers:
  - image: leeroy-app:v2
    name: leeroy-app`).
			WithRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), deploymentAppYAML+"\n"+deploymentWebYAML),
		)

		cfg := &latest.KubectlDeploy{
			Manifests: []string{tmpDir.Path("deployment-app.yaml"), "deployment-web.yaml"},
		}
		deployer := NewKubectlDeployer(&runcontext.RunContext{
			WorkingDir: tmpDir.Root(),
			Cfg: latest.Pipeline{
				Deploy: latest.DeployConfig{
					DeployType: latest.DeployType{
						KubectlDeploy: cfg,
					},
				},
			},
			KubeContext: testKubeContext,
			Opts: config.SkaffoldOptions{
				Namespace: testNamespace,
			},
		})
		labellers := []Labeller{deployer}

		// Deploy one manifest
		err := deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{
			{ImageName: "leeroy-web", Tag: "leeroy-web:v1"},
			{ImageName: "leeroy-app", Tag: "leeroy-app:v1"},
		}, labellers)
		t.CheckNoError(err)

		// Deploy one manifest since only one image is updated
		err = deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{
			{ImageName: "leeroy-web", Tag: "leeroy-web:v1"},
			{ImageName: "leeroy-app", Tag: "leeroy-app:v2"},
		}, labellers)
		t.CheckNoError(err)

		// Deploy zero manifest since no image is updated
		err = deployer.Deploy(context.Background(), ioutil.Discard, []build.Artifact{
			{ImageName: "leeroy-web", Tag: "leeroy-web:v1"},
			{ImageName: "leeroy-app", Tag: "leeroy-app:v2"},
		}, labellers)
		t.CheckNoError(err)
	})
}
<|endoftext|>