<commit_before>package jd

import (
	"testing"
)

func TestSetJson(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		given    string
		want     string
	}{{
		name:     "array with no space",
		metadata: SET,
		given:    `[]`,
		want:     `[]`,
	}, {
		name:     "array with space",
		metadata: SET,
		given:    ` [ ] `,
		want:     `[]`,
	}, {
		name:     "array with numbers out of order",
		metadata: SET,
		given:    `[2,1,3]`,
		want:     `[3,2,1]`,
	}, {
		name:     "array with numbers in order",
		metadata: SET,
		given:    `[3,2,1]`,
		want:     `[3,2,1]`,
	}, {
		name:     "array with spaced numbers",
		metadata: SET,
		given:    ` [1, 2, 3] `,
		want:     `[3,2,1]`,
	}, {
		name:     "duplicate entries",
		metadata: SET,
		given:    `[1,1,1]`,
		want:     `[1]`,
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkJson(ctx, c.given, c.want)
		})
	}
}

func TestSetEquals(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		a        string
		b        string
	}{{
		name:     "empty arrays",
		metadata: SET,
		a:        `[]`,
		b:        `[]`,
	}, {
		name:     "array with numbers unordered 1",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[3,2,1]`,
	}, {
		name:     "array with numbers unordered 2",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[2,3,1]`,
	}, {
		name:     "array with numbers unordered 3",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[1,3,2]`,
	}, {
		name:     "array with empty objects",
		metadata: SET,
		a:        `[{},{}]`,
		b:        `[{},{}]`,
	}, {
		name:     "nested unordered arrays",
		metadata: SET,
		a:        `[[1,2],[3,4]]`,
		b:        `[[2,1],[4,3]]`,
	}, {
		name:     "repeated numbers",
		metadata: SET,
		a:        `[1,1,1]`,
		b:        `[1]`,
	}, {
		name:     "array with numbers repeated and unordered",
		metadata: SET,
		a:        `[1,2,1]`,
		b:        `[2,1,2]`,
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkEqual(ctx, c.a, c.b)
		})
	}
}

func TestSetNotEquals(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		a        string
		b        string
	}{{
		name:     "empty and non-empty sets",
		metadata: SET,
		a:        `[]`,
		b:        `[1]`,
	}, {
		name:     "sets with unique and repeated elements",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[1,2,2]`,
	}, {
		name:     "sets of different sizes",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[1,2]`,
	}, {
		name:     "nested sets with different elements",
		metadata: SET,
		a:        `[[],[1]]`,
		b:        `[[],[2]]`,
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkNotEqual(ctx, c.a, c.b)
		})
	}
}

func TestSetDiff(t *testing.T) {
	cases := []struct {
		name     string
		metadata []Metadata
		a        string
		b        string
		want     []string
	}{{
		name:     "empty sets no diff",
		metadata: m(SET),
		a:        `[]`,
		b:        `[]`,
		want:     s(``),
	}, {
		name:     "add a number",
		metadata: m(SET),
		a:        `[1]`,
		b:        `[1,2]`,
		want: s(
			`@ [["set"],{}]`,
			`+ 2`,
		),
	}, {
		name:     "sets with same numbers",
		metadata: m(SET),
		a:        `[1,2]`,
		b:        `[1,2]`,
		want:     s(``),
	}, {
		name:     "add a number multiple times",
		metadata: m(SET),
		a:        `[1]`,
		b:        `[1,2,2]`,
		want: s(
			`@ [["set"],{}]`,
			`+ 2`,
		),
	}, {
		name:     "remove a number",
		metadata: m(SET),
		a:        `[1,2,3]`,
		b:        `[1,3]`,
		want: s(
			`@ [["set"],{}]`,
			`- 2`,
		),
	}, {
		name:     "replace one object with another",
		metadata: m(SET),
		a:        `[{"a":1}]`,
		b:        `[{"a":2}]`,
		want: s(
			`@ [["set"],{}]`,
			`- {"a":1}`,
			`+ {"a":2}`,
		),
	}, {
		name:     "replace one repeated object with another",
		metadata: m(SET),
		a:        `[{"a":1},{"a":1}]`,
		b:        `[{"a":2}]`,
		want: s(
			`@ [["set"],{}]`,
			`- {"a":1}`,
			`+ {"a":2}`,
		),
	}, {
		name:     "remove two strings and add one",
		metadata: m(SET),
		a:        `["foo","foo","bar"]`,
		b:        `["baz"]`,
		want: s(
			`@ [["set"],{}]`,
			`- "bar"`,
			`- "foo"`,
			`+ "baz"`,
		),
	}, {
		name:     "remove one string and add two repeated",
		metadata: m(SET),
		a:        `["foo"]`,
		b:        `["bar","baz","bar"]`,
		want: s(
			`@ [["set"],{}]`,
			`- "foo"`,
			`+ "bar"`,
			`+ "baz"`,
		),
	}, {
		name:     "remove object and add array",
		metadata: m(SET),
		a:        `{}`,
		b:        `[]`,
		want: s(
			`@ []`,
			`- {}`,
			`+ []`,
		),
	}, {
		name: "add property to object in set",
		metadata: m(
			SET,
			Setkeys("id"),
		),
		a: `[{"id":"foo"}]`,
		b: `[{"id":"foo","bar":"baz"}]`,
		want: s(
			`@ [["set","setkeys=id"],{"id":"foo"},"bar"]`,
			`+ "baz"`,
		),
	}, {
		name: "find object by id among empty objects",
		metadata: m(
			SET,
			Setkeys("id"),
		),
		a: `[{},{},{"id":"foo"},{}]`,
		b: `[{},{"id":"foo","bar":"baz"},{},{}]`,
		want: s(
			`@ [["set","setkeys=id"],{"id":"foo"},"bar"]`,
			`+ "baz"`,
		),
	}, {
		name: "find object by multiple ids",
		metadata: m(
			SET,
			Setkeys("id1", "id2"),
		),
		a: `[{},{"id1":"foo","id2":"zap"},{}]`,
		b: `[{},{"id1":"foo","id2":"zap","bar":"baz"},{}]`,
		want: s(
			`@ [["set","setkeys=id1,id2"],{"id1":"foo","id2":"zap"},"bar"]`,
			`+ "baz"`,
		),
	}, {
		name: "find object by id among others",
		metadata: m(
			SET,
			Setkeys("id"),
		),
		a: `[{"id":"foo"},{"id":"bar"}]`,
		b: `[{"id":"foo","baz":"zap"},{"id":"bar"}]`,
		want: s(
			`@ [["set","setkeys=id"],{"id":"foo"},"baz"]`,
			`+ "zap"`,
		),
	}, {
		name: "two objects with different ids being exchanged",
		metadata: m(
			SET,
			Setkeys("id"),
		),
		a: `[{"id":"foo"}]`,
		b: `[{"id":"bar"}]`,
		want: s(
			`@ [["set","setkeys=id"],{}]`,
			`- {"id":"foo"}`,
			`+ {"id":"bar"}`,
		),
	}, {
		name:     "set metadata applies to array in object",
		metadata: m(SET),
		a:        `{"a":[1,2]}`,
		b:        `{"a":[2,1]}`,
		want:     s(``),
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata...)
			checkDiff(ctx, c.a, c.b, c.want...)
		})
	}
}

func TestSetPatch(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		given    string
		patch    []string
		want     string
	}{{
		name:     "empty patch on empty set does nothing",
		metadata: SET,
		given:    `[]`,
		patch:    s(``),
		want:     `[]`,
	}, {
		name:     "add a number",
		metadata: SET,
		given:    `[1]`,
		patch: s(
			`@ [{}]`,
			`+ 2`,
		),
		want: `[1,2]`,
	}, {
		name:     "empty patch on set with numbers does nothing",
		metadata: SET,
		given:    `[1,2]`,
		patch:    s(``),
		want:     `[1,2]`,
	}, {
		name:     "remove a number from a set",
		metadata: SET,
		given:    `[1,2,3]`,
		patch: s(
			`@ [{}]`,
			`- 2`,
		),
		want: `[1,3]`,
	}, {
		name:     "replace one object with another",
		metadata: SET,
		given:    `[{"a":1}]`,
		patch: s(
			`@ [{}]`,
			`- {"a":1}`,
			`+ {"a":2}`,
		),
		want: `[{"a":2}]`,
	}, {
		name:     "replace one repeated object with another",
		metadata: SET,
		given:    `[{"a":1},{"a":1}]`,
		patch: s(
			`@ [{}]`,
			`- {"a":1}`,
			`+ {"a":2}`,
		),
		want: `[{"a":2}]`,
	}, {
		name:     "replace two strings with one string",
		metadata: SET,
		given:    `["foo","foo","bar"]`,
		patch: s(
			`@ [{}]`,
			`- "bar"`,
			`- "foo"`,
			`+ "baz"`,
		),
		want: `["baz"]`,
	}, {
		name:     "replace one string with two strings",
		metadata: SET,
		given:    `["foo"]`,
		patch: s(
			`@ [{}]`,
			`- "foo"`,
			`+ "bar"`,
			`+ "baz"`,
		),
		want: `["bar","baz","bar"]`,
	}, {
		name:     "replace object with array",
		metadata: SET,
		given:    `{}`,
		patch: s(
			`@ []`,
			`- {}`,
			`+ []`,
		),
		want: `[]`,
	}, {
		name:     "patch object by id",
		metadata: SET,
		given:    `[{"id":"foo"}]`,
		patch: s(
			`@ [["set"],{"id":"foo"},"bar"]`,
			`+ "baz"`,
		),
		want: `[{"id":"foo","bar":"baz"}]`,
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkPatch(ctx, c.given, c.want, c.patch...)
		})
	}
}

func TestSetPatchError(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		given    string
		patch    []string
	}{{
		name:     "removing number from empty set",
		metadata: SET,
		given:    `[]`,
		patch: s(
			`@ [{}]`,
			`- 1`,
		),
	}, {
		name:     "removing number from set twice",
		metadata: SET,
		given:    `[1]`,
		patch: s(
			`@ [{}]`,
			`- 1`,
			`- 1`,
		),
	}, {
		name:     "removing object from empty set",
		metadata: SET,
		given:    `[]`,
		patch: s(
			`@ []`,
			`- {}`,
		),
	}, {
		name:     "removing number from set twice added twice",
		metadata: SET,
		given:    `[]`,
		patch: s(
			`@ [{}]`,
			`+ 1`,
			`+ 1`,
			`@ [{}]`,
			`- 1`,
			`- 1`,
		),
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkPatchError(ctx, c.given, c.patch...)
		})
	}
}
<commit_msg>Additional patch tests with setkeys.<commit_after>package jd

import (
	"testing"
)

func TestSetJson(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		given    string
		want     string
	}{{
		name:     "array with no space",
		metadata: SET,
		given:    `[]`,
		want:     `[]`,
	}, {
		name:     "array with space",
		metadata: SET,
		given:    ` [ ] `,
		want:     `[]`,
	}, {
		name:     "array with numbers out of order",
		metadata: SET,
		given:    `[2,1,3]`,
		want:     `[3,2,1]`,
	}, {
		name:     "array with numbers in order",
		metadata: SET,
		given:    `[3,2,1]`,
		want:     `[3,2,1]`,
	}, {
		name:     "array with spaced numbers",
		metadata: SET,
		given:    ` [1, 2, 3] `,
		want:     `[3,2,1]`,
	}, {
		name:     "duplicate entries",
		metadata: SET,
		given:    `[1,1,1]`,
		want:     `[1]`,
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkJson(ctx, c.given, c.want)
		})
	}
}

func TestSetEquals(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		a        string
		b        string
	}{{
		name:     "empty arrays",
		metadata: SET,
		a:        `[]`,
		b:        `[]`,
	}, {
		name:     "array with numbers unordered 1",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[3,2,1]`,
	}, {
		name:     "array with numbers unordered 2",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[2,3,1]`,
	}, {
		name:     "array with numbers unordered 3",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[1,3,2]`,
	}, {
		name:     "array with empty objects",
		metadata: SET,
		a:        `[{},{}]`,
		b:        `[{},{}]`,
	}, {
		name:     "nested unordered arrays",
		metadata: SET,
		a:        `[[1,2],[3,4]]`,
		b:        `[[2,1],[4,3]]`,
	}, {
		name:     "repeated numbers",
		metadata: SET,
		a:        `[1,1,1]`,
		b:        `[1]`,
	}, {
		name:     "array with numbers repeated and unordered",
		metadata: SET,
		a:        `[1,2,1]`,
		b:        `[2,1,2]`,
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkEqual(ctx, c.a, c.b)
		})
	}
}

func TestSetNotEquals(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		a        string
		b        string
	}{{
		name:     "empty and non-empty sets",
		metadata: SET,
		a:        `[]`,
		b:        `[1]`,
	}, {
		name:     "sets with unique and repeated elements",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[1,2,2]`,
	}, {
		name:     "sets of different sizes",
		metadata: SET,
		a:        `[1,2,3]`,
		b:        `[1,2]`,
	}, {
		name:     "nested sets with different elements",
		metadata: SET,
		a:        `[[],[1]]`,
		b:        `[[],[2]]`,
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkNotEqual(ctx, c.a, c.b)
		})
	}
}

func TestSetDiff(t *testing.T) {
	cases := []struct {
		name     string
		metadata []Metadata
		a        string
		b        string
		want     []string
	}{{
		name:     "empty sets no diff",
		metadata: m(SET),
		a:        `[]`,
		b:        `[]`,
		want:     s(``),
	}, {
		name:     "add a number",
		metadata: m(SET),
		a:        `[1]`,
		b:        `[1,2]`,
		want: s(
			`@ [["set"],{}]`,
			`+ 2`,
		),
	}, {
		name:     "sets with same numbers",
		metadata: m(SET),
		a:        `[1,2]`,
		b:        `[1,2]`,
		want:     s(``),
	}, {
		name:     "add a number multiple times",
		metadata: m(SET),
		a:        `[1]`,
		b:        `[1,2,2]`,
		want: s(
			`@ [["set"],{}]`,
			`+ 2`,
		),
	}, {
		name:     "remove a number",
		metadata: m(SET),
		a:        `[1,2,3]`,
		b:        `[1,3]`,
		want: s(
			`@ [["set"],{}]`,
			`- 2`,
		),
	}, {
		name:     "replace one object with another",
		metadata: m(SET),
		a:        `[{"a":1}]`,
		b:        `[{"a":2}]`,
		want: s(
			`@ [["set"],{}]`,
			`- {"a":1}`,
			`+ {"a":2}`,
		),
	}, {
		name:     "replace one repeated object with another",
		metadata: m(SET),
		a:        `[{"a":1},{"a":1}]`,
		b:        `[{"a":2}]`,
		want: s(
			`@ [["set"],{}]`,
			`- {"a":1}`,
			`+ {"a":2}`,
		),
	}, {
		name:     "remove two strings and add one",
		metadata: m(SET),
		a:        `["foo","foo","bar"]`,
		b:        `["baz"]`,
		want: s(
			`@ [["set"],{}]`,
			`- "bar"`,
			`- "foo"`,
			`+ "baz"`,
		),
	}, {
		name:     "remove one string and add two repeated",
		metadata: m(SET),
		a:        `["foo"]`,
		b:        `["bar","baz","bar"]`,
		want: s(
			`@ [["set"],{}]`,
			`- "foo"`,
			`+ "bar"`,
			`+ "baz"`,
		),
	}, {
		name:     "remove object and add array",
		metadata: m(SET),
		a:        `{}`,
		b:        `[]`,
		want: s(
			`@ []`,
			`- {}`,
			`+ []`,
		),
	}, {
		name: "add property to object in set",
		metadata: m(
			SET,
			Setkeys("id"),
		),
		a: `[{"id":"foo"}]`,
		b: `[{"id":"foo","bar":"baz"}]`,
		want: s(
			`@ [["set","setkeys=id"],{"id":"foo"},"bar"]`,
			`+ "baz"`,
		),
	}, {
		name: "find object by id among empty objects",
		metadata: m(
			SET,
			Setkeys("id"),
		),
		a: `[{},{},{"id":"foo"},{}]`,
		b: `[{},{"id":"foo","bar":"baz"},{},{}]`,
		want: s(
			`@ [["set","setkeys=id"],{"id":"foo"},"bar"]`,
			`+ "baz"`,
		),
	}, {
		name: "find object by multiple ids",
		metadata: m(
			SET,
			Setkeys("id1", "id2"),
		),
		a: `[{},{"id1":"foo","id2":"zap"},{}]`,
		b: `[{},{"id1":"foo","id2":"zap","bar":"baz"},{}]`,
		want: s(
			`@ [["set","setkeys=id1,id2"],{"id1":"foo","id2":"zap"},"bar"]`,
			`+ "baz"`,
		),
	}, {
		name: "find object by id among others",
		metadata: m(
			SET,
			Setkeys("id"),
		),
		a: `[{"id":"foo"},{"id":"bar"}]`,
		b: `[{"id":"foo","baz":"zap"},{"id":"bar"}]`,
		want: s(
			`@ [["set","setkeys=id"],{"id":"foo"},"baz"]`,
			`+ "zap"`,
		),
	}, {
		name: "two objects with different ids being exchanged",
		metadata: m(
			SET,
			Setkeys("id"),
		),
		a: `[{"id":"foo"}]`,
		b: `[{"id":"bar"}]`,
		want: s(
			`@ [["set","setkeys=id"],{}]`,
			`- {"id":"foo"}`,
			`+ {"id":"bar"}`,
		),
	}, {
		name:     "set metadata applies to array in object",
		metadata: m(SET),
		a:        `{"a":[1,2]}`,
		b:        `{"a":[2,1]}`,
		want:     s(``),
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata...)
			checkDiff(ctx, c.a, c.b, c.want...)
		})
	}
}

func TestSetPatch(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		given    string
		patch    []string
		want     string
	}{{
		name:     "empty patch on empty set does nothing",
		metadata: SET,
		given:    `[]`,
		patch:    s(``),
		want:     `[]`,
	}, {
		name:     "add a number",
		metadata: SET,
		given:    `[1]`,
		patch: s(
			`@ [{}]`,
			`+ 2`,
		),
		want: `[1,2]`,
	}, {
		name:     "empty patch on set with numbers does nothing",
		metadata: SET,
		given:    `[1,2]`,
		patch:    s(``),
		want:     `[1,2]`,
	}, {
		name:     "remove a number from a set",
		metadata: SET,
		given:    `[1,2,3]`,
		patch: s(
			`@ [{}]`,
			`- 2`,
		),
		want: `[1,3]`,
	}, {
		name:     "replace one object with another",
		metadata: SET,
		given:    `[{"a":1}]`,
		patch: s(
			`@ [{}]`,
			`- {"a":1}`,
			`+ {"a":2}`,
		),
		want: `[{"a":2}]`,
	}, {
		name:     "replace one repeated object with another",
		metadata: SET,
		given:    `[{"a":1},{"a":1}]`,
		patch: s(
			`@ [{}]`,
			`- {"a":1}`,
			`+ {"a":2}`,
		),
		want: `[{"a":2}]`,
	}, {
		name:     "replace two strings with one string",
		metadata: SET,
		given:    `["foo","foo","bar"]`,
		patch: s(
			`@ [{}]`,
			`- "bar"`,
			`- "foo"`,
			`+ "baz"`,
		),
		want: `["baz"]`,
	}, {
		name:     "replace one string with two strings",
		metadata: SET,
		given:    `["foo"]`,
		patch: s(
			`@ [{}]`,
			`- "foo"`,
			`+ "bar"`,
			`+ "baz"`,
		),
		want: `["bar","baz","bar"]`,
	}, {
		name:     "replace object with array",
		metadata: SET,
		given:    `{}`,
		patch: s(
			`@ []`,
			`- {}`,
			`+ []`,
		),
		want: `[]`,
	}, {
		name:     "patch property to object in set",
		metadata: SET,
		given:    `[{"id":"foo"}]`,
		patch: s(
			`@ [["set"],{"id":"foo"},"bar"]`,
			`+ "baz"`,
		),
		want: `[{"id":"foo","bar":"baz"}]`,
	}, {
		name:     "patch object among empty objects",
		metadata: SET,
		given:    `[{},{},{"id":"foo"},{}]`,
		patch: s(
			`@ [["set","setkeys=id"],{"id":"foo"},"bar"]`,
			`+ "baz"`,
		),
		want: `[{},{"id":"foo","bar":"baz"},{},{}]`,
	}, {
		name:     "patch object by multiple ids",
		metadata: SET,
		given:    `[{},{"id1":"foo","id2":"zap"},{}]`,
		patch: s(
			`@ [["set","setkeys=id1,id2"],{"id1":"foo","id2":"zap"},"bar"]`,
			`+ "baz"`,
		),
		want: `[{},{"id1":"foo","id2":"zap","bar":"baz"},{}]`,
	}, {
		name:     "patch object by id among other",
		metadata: SET,
		given:    `[{"id":"foo"},{"id":"bar"}]`,
		patch: s(
			`@ [["set","setkeys=id"],{"id":"foo"},"baz"]`,
			`+ "zap"`,
		),
		want: `[{"id":"foo","baz":"zap"},{"id":"bar"}]`,
	}, {
		name:     "replace two objects with diffent ids",
		metadata: SET,
		given:    `[{"id":"foo"}]`,
		patch: s(
			`@ [["set","setkeys=id"],{}]`,
			`- {"id":"foo"}`,
			`+ {"id":"bar"}`,
		),
		want: `[{"id":"bar"}]`,
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkPatch(ctx, c.given, c.want, c.patch...)
		})
	}
}

func TestSetPatchError(t *testing.T) {
	cases := []struct {
		name     string
		metadata Metadata
		given    string
		patch    []string
	}{{
		name:     "removing number from empty set",
		metadata: SET,
		given:    `[]`,
		patch: s(
			`@ [{}]`,
			`- 1`,
		),
	}, {
		name:     "removing number from set twice",
		metadata: SET,
		given:    `[1]`,
		patch: s(
			`@ [{}]`,
			`- 1`,
			`- 1`,
		),
	}, {
		name:     "removing object from empty set",
		metadata: SET,
		given:    `[]`,
		patch: s(
			`@ []`,
			`- {}`,
		),
	}, {
		name:     "removing number from set twice added twice",
		metadata: SET,
		given:    `[]`,
		patch: s(
			`@ [{}]`,
			`+ 1`,
			`+ 1`,
			`@ [{}]`,
			`- 1`,
			`- 1`,
		),
	}}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			ctx := newTestContext(t).
				withMetadata(c.metadata)
			checkPatchError(ctx, c.given, c.patch...)
		})
	}
}
<|endoftext|>


<commit_before>package paperless

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"

	"github.com/kopoli/go-util"
)

type db struct {
	file string
	*sqlx.DB
}

type dbTx struct {
	*sqlx.Tx
}

// Pagination support
type Page struct {
	// Id that was the last of the previous page
	SinceId int

	// Count is the number of items in the page
	Count int
}

type Search struct {
	ID      int
	OrderBy string
	Match   string
}

func openDbFile(dbfile string) (ret *db, err error) {
	create := false

	dbfile = filepath.Clean(dbfile)

	i, err := os.Stat(dbfile)
	if err == nil && i.IsDir() {
		err = util.E.New("Given path is a directory")
		return
	}

	err = MkdirParents(dbfile)
	if err != nil {
		err = util.E.Annotate(err,
			"Could not create dbfile's parent directories")
		return
	}

	if _, err = os.Stat(dbfile); os.IsNotExist(err) {
		create = true
		err = nil
	}

	d, err := sqlx.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc", dbfile))
	if err != nil {
		err = util.E.Annotate(err, "Opening sqlite dbfile failed")
		return
	}

	if create {
		_, err = d.Exec(`
CREATE TABLE IF NOT EXISTS tag (
  id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
  name TEXT DEFAULT "" NOT NULL UNIQUE ON CONFLICT ABORT,
  comment TEXT DEFAULT ""
);

-- The image data
CREATE TABLE IF NOT EXISTS image (
  id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
  checksum TEXT UNIQUE NOT NULL ON CONFLICT ABORT,-- checksum of the file
  fileid TEXT DEFAULT "",                       -- used to construct the processed image,
						--   thumbnail and text files
  scandate DATETIME,                            -- timestamp when it was scanned
  adddate  DATETIME DEFAULT CURRENT_TIMESTAMP,  -- timestamp when it was created in db
  interpretdate DATETIME,                       -- timestamp when it was interpret

  processlog TEXT DEFAULT "",                   -- Log of processing
  filename TEXT DEFAULT ""                     -- The original filename
);

CREATE VIRTUAL TABLE IF NOT EXISTS imgtext USING fts4 (
  text DEFAULT "",				-- the OCR'd text
  comment DEFAULT ""				-- freeform comment
);

-- Tags for an image
CREATE TABLE IF NOT EXISTS imgtag (
  tagid INTEGER REFERENCES tag(id) NOT NULL,
  imgid INTEGER REFERENCES img(id) NOT NULL,
  UNIQUE (tagid, imgid)
);

-- Script for processing the images
CREATE TABLE IF NOT EXISTS script (
  id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
  name TEXT UNIQUE ON CONFLICT ABORT,
  script TEXT DEFAULT ""
);

`)
		if err != nil {
			goto initfail
		}

	}
	d.Exec("PRAGMA busy_timeout=10000")
	if err != nil {
		goto initfail
	}

	ret = &db{dbfile, d}
	return

initfail:
	d.Close()
	err = util.E.Annotate(err, "Initializing the database failed")
	ret = nil
	return
}

func (db *db) getTag(id int) (ret Tag, err error) {
	err = db.Get(&ret, "SELECT * from tag WHERE id = $1", id)
	return
}

func (db *db) getScript(id int) (ret Script, err error) {
	err = db.Get(&ret, "SELECT * from script WHERE id = $1", id)
	return
}

func (db *db) getImage(id int) (ret Image, err error) {
	if id < 0 {
		err = util.E.New("Negative ID for image is invalid")
		return
	}

	imgs, err := db.getImages(nil, &Search{ID: id})
	if err != nil {
		return
	}

	if len(imgs) == 0 {
		err = util.E.New("No image found with id %d", id)
		return
	}
	if len(imgs) > 1 {
		err = util.E.New("Internal error: Multiple images with the same id")
		return
	}
	ret = imgs[0]
	return
}

func (db *db) getTags(p *Page) (ret []Tag, err error) {
	query := "SELECT * from tag"
	order := " ORDER BY name ASC"
	sel := func() error {
		return db.Select(&ret, query+order)
	}

	if p != nil {
		query += " WHERE (id > ?) " + order + " LIMIT ?"
		sel = func() error {
			return db.Select(&ret, query, p.SinceId, p.Count)
		}
	}

	err = sel()
	return
}

func (db *db) addTag(t Tag) (ret Tag, err error) {
	_, err = db.Exec("INSERT INTO tag(name, comment) VALUES($1, $2)", t.Name, t.Comment)
	if err != nil {
		return
	}
	err = db.Get(&ret, "SELECT * FROM tag WHERE name = $1", t.Name)

	return
}

func (db *db) updateTag(t Tag) (err error) {
	_, err = db.Exec("UPDATE tag SET comment = $1 WHERE name = $2", t.Comment, t.Name)
	return
}

func (db *db) deleteTag(t Tag) (err error) {
	_, err = db.Exec("DELETE FROM tag WHERE name = $1", t.Name)
	return
}

func (db *db) getScripts(p *Page) (ret []Script, err error) {
	query := "SELECT * from script"
	order := " ORDER BY name ASC"
	sel := func() error {
		return db.Select(&ret, query+order)
	}

	if p != nil {
		query += " WHERE (id > ?) " + order + " LIMIT ?"
		sel = func() error {
			return db.Select(&ret, query, p.SinceId, p.Count)
		}
	}

	err = sel()
	return
}

func (db *db) addScript(s Script) (ret Script, err error) {
	_, err = db.Exec("INSERT INTO script(name, script) VALUES($1, $2)", s.Name, s.Script)
	if err != nil {
		return
	}

	err = db.Get(&ret, "SELECT * FROM script WHERE name = $1", s.Name)
	return
}

func (db *db) updateScript(s Script) (err error) {
	_, err = db.Exec("UPDATE script SET script = $1 WHERE name = $2", s.Script, s.Name)
	return
}

func (db *db) deleteScript(s Script) (err error) {
	_, err = db.Exec("DELETE FROM script WHERE name = $1", s.Name)
	return
}

func withTx(db *db, f func(*sqlx.Tx) error) (err error) {
	tx, err := db.Beginx()
	if err != nil {
		return
	}

	err = f(tx)
	if err != nil {
		tx.Rollback()
		return
	}

	err = tx.Commit()
	return
}

func (db *db) getImages(p *Page, s *Search) (ret []Image, err error) {
	query := "SELECT * FROM image, imgtext"
	order := " ORDER BY image.id ASC"

	where := " WHERE imgtext.rowid = image.id"

	args := map[string]interface{}{}

	if s != nil {
		if s.ID != 0 {
			where = where + " AND image.id = :id"
			args["id"] = fmt.Sprintf("%d", s.ID)
		}
		if s.Match != "" {
			where = where + " AND imgtext.text MATCH :match"
			args["match"] = s.Match
		}
		if s.OrderBy != "" {
			order = " ORDER BY :order ASC"
			args["order"] = s.OrderBy
		}
	}
	if p != nil {
		where = where + " AND (image.id > :id)"
		args["id"] = fmt.Sprintf("%d", p.SinceId)
		order = order + " LIMIT :limit"
		args["limit"] = fmt.Sprintf("%d", p.Count)
	}

	query = query + where + order

	nstmt, err := db.PrepareNamed(query)
	if err != nil {
		return
	}
	defer nstmt.Close()

	err = nstmt.Select(&ret, args)
	if err != nil {
		return
	}

	err = withTx(db, func(tx *sqlx.Tx) (err error) {
		for i := range ret {
			err = tx.Select(&ret[i].Tags, `SELECT tag.id, tag.name, tag.comment FROM tag, imgtag
                                                       WHERE imgtag.tagid = tag.id AND imgtag.imgid = $1 `, ret[i].Id)
			if err != nil {
				return
			}
		}
		return
	})
	return
}

func syncTagsToImage(tx *sqlx.Tx, i Image) (err error) {
	_, err = tx.NamedExec(`DELETE FROM imgtag WHERE imgid = :id`, i)
	if err != nil {
		return
	}

	for _, t := range i.Tags {
		_, err = tx.Exec(`INSERT INTO imgtag(imgid, tagid) SELECT $1, tag.id FROM tag WHERE tag.name = $2`, i.Id, t.Name)
		if err != nil {
			return
		}
	}
	return
}

func (db *db) addImage(i Image) (ret Image, err error) {
	err = withTx(db, func(tx *sqlx.Tx) (err error) {
		_, err = tx.NamedExec(`INSERT INTO
                   image(  checksum,  fileid,  scandate,  adddate,  interpretdate,  processlog,  filename)
                   VALUES(:checksum, :fileid, :scandate, :adddate, :interpretdate, :processlog, :filename)`, i)
		if err != nil {
			return
		}

		var id int
		err = tx.Get(&id, "SELECT id FROM image WHERE checksum=$1", i.Checksum)
		if err != nil {
			return
		}
		i.Id = id

		_, err = tx.NamedExec(`INSERT INTO imgtext(rowid, text, comment) VALUES (:id, :text, :comment)`, i)
		if err != nil {
			return
		}

		err = syncTagsToImage(tx, i)
		ret = i
		return
	})
	return
}

func (db *db) updateImage(i Image) (err error) {
	err = withTx(db, func(tx *sqlx.Tx) (err error) {
		_, err = tx.NamedExec(`UPDATE image SET
                      interpretdate = :interpretdate,
                      processlog = :processlog
                      WHERE image.id = :id`, i)
		if err != nil {
			return
		}

		_, err = tx.NamedExec(`UPDATE imgtext SET
                      text = :text,
                      comment = :comment
                      WHERE rowid = :id`, i)
		if err != nil {
			return
		}

		err = syncTagsToImage(tx, i)
		return
	})
	return
}

func (db *db) deleteImage(s Image) (err error) {
	err = withTx(db, func(tx *sqlx.Tx) (err error) {
		_, err = tx.Exec(`DELETE FROM imgtag WHERE imgid IN
                                  (SELECT id FROM image WHERE image.checksum = $1)`, s.Checksum)
		if err != nil {
			return
		}
		_, err = tx.Exec(`DELETE FROM imgtext WHERE rowid IN
                                  (SELECT id FROM image WHERE image.checksum = $1)`, s.Checksum)
		if err != nil {
			return
		}
		_, err = tx.Exec(`DELETE FROM image WHERE image.checksum = $1`, s.Checksum)
		return
	})
	return
}
<commit_msg>sqlitedb: this should prevent database locking<commit_after>package paperless

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"

	"github.com/kopoli/go-util"
)

type db struct {
	file string
	*sqlx.DB
}

type dbTx struct {
	*sqlx.Tx
}

// Pagination support
type Page struct {
	// Id that was the last of the previous page
	SinceId int

	// Count is the number of items in the page
	Count int
}

type Search struct {
	ID      int
	OrderBy string
	Match   string
}

func openDbFile(dbfile string) (ret *db, err error) {
	create := false

	dbfile = filepath.Clean(dbfile)

	i, err := os.Stat(dbfile)
	if err == nil && i.IsDir() {
		err = util.E.New("Given path is a directory")
		return
	}

	err = MkdirParents(dbfile)
	if err != nil {
		err = util.E.Annotate(err,
			"Could not create dbfile's parent directories")
		return
	}

	if _, err = os.Stat(dbfile); os.IsNotExist(err) {
		create = true
		err = nil
	}

	d, err := sqlx.Open("sqlite3", fmt.Sprintf("file:%s?cache=shared&mode=rwc", dbfile))
	if err != nil {
		err = util.E.Annotate(err, "Opening sqlite dbfile failed")
		return
	}

	if create {
		_, err = d.Exec(`
CREATE TABLE IF NOT EXISTS tag (
  id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
  name TEXT DEFAULT "" NOT NULL UNIQUE ON CONFLICT ABORT,
  comment TEXT DEFAULT ""
);

-- The image data
CREATE TABLE IF NOT EXISTS image (
  id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
  checksum TEXT UNIQUE NOT NULL ON CONFLICT ABORT,-- checksum of the file
  fileid TEXT DEFAULT "",                       -- used to construct the processed image,
						--   thumbnail and text files
  scandate DATETIME,                            -- timestamp when it was scanned
  adddate  DATETIME DEFAULT CURRENT_TIMESTAMP,  -- timestamp when it was created in db
  interpretdate DATETIME,                       -- timestamp when it was interpret

  processlog TEXT DEFAULT "",                   -- Log of processing
  filename TEXT DEFAULT ""                     -- The original filename
);

CREATE VIRTUAL TABLE IF NOT EXISTS imgtext USING fts4 (
  text DEFAULT "",				-- the OCR'd text
  comment DEFAULT ""				-- freeform comment
);

-- Tags for an image
CREATE TABLE IF NOT EXISTS imgtag (
  tagid INTEGER REFERENCES tag(id) NOT NULL,
  imgid INTEGER REFERENCES img(id) NOT NULL,
  UNIQUE (tagid, imgid)
);

-- Script for processing the images
CREATE TABLE IF NOT EXISTS script (
  id INTEGER PRIMARY KEY ASC AUTOINCREMENT,
  name TEXT UNIQUE ON CONFLICT ABORT,
  script TEXT DEFAULT ""
);

`)
		if err != nil {
			goto initfail
		}

	}
	_, err = d.Exec("PRAGMA busy_timeout=10000")
	if err != nil {
		goto initfail
	}

	// Work around the multiple access problems
	d.SetMaxOpenConns(1)

	ret = &db{dbfile, d}
	return

initfail:
	d.Close()
	err = util.E.Annotate(err, "Initializing the database failed")
	ret = nil
	return
}

func (db *db) getTag(id int) (ret Tag, err error) {
	err = db.Get(&ret, "SELECT * from tag WHERE id = $1", id)
	return
}

func (db *db) getScript(id int) (ret Script, err error) {
	err = db.Get(&ret, "SELECT * from script WHERE id = $1", id)
	return
}

func (db *db) getImage(id int) (ret Image, err error) {
	if id < 0 {
		err = util.E.New("Negative ID for image is invalid")
		return
	}

	imgs, err := db.getImages(nil, &Search{ID: id})
	if err != nil {
		return
	}

	if len(imgs) == 0 {
		err = util.E.New("No image found with id %d", id)
		return
	}
	if len(imgs) > 1 {
		err = util.E.New("Internal error: Multiple images with the same id")
		return
	}
	ret = imgs[0]
	return
}

func (db *db) getTags(p *Page) (ret []Tag, err error) {
	query := "SELECT * from tag"
	order := " ORDER BY name ASC"
	sel := func() error {
		return db.Select(&ret, query+order)
	}

	if p != nil {
		query += " WHERE (id > ?) " + order + " LIMIT ?"
		sel = func() error {
			return db.Select(&ret, query, p.SinceId, p.Count)
		}
	}

	err = sel()
	return
}

func (db *db) addTag(t Tag) (ret Tag, err error) {
	_, err = db.Exec("INSERT INTO tag(name, comment) VALUES($1, $2)", t.Name, t.Comment)
	if err != nil {
		return
	}
	err = db.Get(&ret, "SELECT * FROM tag WHERE name = $1", t.Name)

	return
}

func (db *db) updateTag(t Tag) (err error) {
	_, err = db.Exec("UPDATE tag SET comment = $1 WHERE name = $2", t.Comment, t.Name)
	return
}

func (db *db) deleteTag(t Tag) (err error) {
	_, err = db.Exec("DELETE FROM tag WHERE name = $1", t.Name)
	return
}

func (db *db) getScripts(p *Page) (ret []Script, err error) {
	query := "SELECT * from script"
	order := " ORDER BY name ASC"
	sel := func() error {
		return db.Select(&ret, query+order)
	}

	if p != nil {
		query += " WHERE (id > ?) " + order + " LIMIT ?"
		sel = func() error {
			return db.Select(&ret, query, p.SinceId, p.Count)
		}
	}

	err = sel()
	return
}

func (db *db) addScript(s Script) (ret Script, err error) {
	_, err = db.Exec("INSERT INTO script(name, script) VALUES($1, $2)", s.Name, s.Script)
	if err != nil {
		return
	}

	err = db.Get(&ret, "SELECT * FROM script WHERE name = $1", s.Name)
	return
}

func (db *db) updateScript(s Script) (err error) {
	_, err = db.Exec("UPDATE script SET script = $1 WHERE name = $2", s.Script, s.Name)
	return
}

func (db *db) deleteScript(s Script) (err error) {
	_, err = db.Exec("DELETE FROM script WHERE name = $1", s.Name)
	return
}

func withTx(db *db, f func(*sqlx.Tx) error) (err error) {
	tx, err := db.Beginx()
	if err != nil {
		return
	}

	err = f(tx)
	if err != nil {
		tx.Rollback()
		return
	}

	err = tx.Commit()
	return
}

func (db *db) getImages(p *Page, s *Search) (ret []Image, err error) {
	query := "SELECT * FROM image, imgtext"
	order := " ORDER BY image.id ASC"

	where := " WHERE imgtext.rowid = image.id"

	args := map[string]interface{}{}

	if s != nil {
		if s.ID != 0 {
			where = where + " AND image.id = :id"
			args["id"] = fmt.Sprintf("%d", s.ID)
		}
		if s.Match != "" {
			where = where + " AND imgtext.text MATCH :match"
			args["match"] = s.Match
		}
		if s.OrderBy != "" {
			order = " ORDER BY :order ASC"
			args["order"] = s.OrderBy
		}
	}
	if p != nil {
		where = where + " AND (image.id > :id)"
		args["id"] = fmt.Sprintf("%d", p.SinceId)
		order = order + " LIMIT :limit"
		args["limit"] = fmt.Sprintf("%d", p.Count)
	}

	query = query + where + order

	nstmt, err := db.PrepareNamed(query)
	if err != nil {
		return
	}
	defer nstmt.Close()

	err = nstmt.Select(&ret, args)
	if err != nil {
		return
	}

	err = withTx(db, func(tx *sqlx.Tx) (err error) {
		for i := range ret {
			err = tx.Select(&ret[i].Tags, `SELECT tag.id, tag.name, tag.comment FROM tag, imgtag
                                                       WHERE imgtag.tagid = tag.id AND imgtag.imgid = $1 `, ret[i].Id)
			if err != nil {
				return
			}
		}
		return
	})
	return
}

func syncTagsToImage(tx *sqlx.Tx, i Image) (err error) {
	_, err = tx.NamedExec(`DELETE FROM imgtag WHERE imgid = :id`, i)
	if err != nil {
		return
	}

	for _, t := range i.Tags {
		_, err = tx.Exec(`INSERT INTO imgtag(imgid, tagid) SELECT $1, tag.id FROM tag WHERE tag.name = $2`, i.Id, t.Name)
		if err != nil {
			return
		}
	}
	return
}

func (db *db) addImage(i Image) (ret Image, err error) {
	err = withTx(db, func(tx *sqlx.Tx) (err error) {
		_, err = tx.NamedExec(`INSERT INTO
                   image(  checksum,  fileid,  scandate,  adddate,  interpretdate,  processlog,  filename)
                   VALUES(:checksum, :fileid, :scandate, :adddate, :interpretdate, :processlog, :filename)`, i)
		if err != nil {
			return
		}

		var id int
		err = tx.Get(&id, "SELECT id FROM image WHERE checksum=$1", i.Checksum)
		if err != nil {
			return
		}
		i.Id = id

		_, err = tx.NamedExec(`INSERT INTO imgtext(rowid, text, comment) VALUES (:id, :text, :comment)`, i)
		if err != nil {
			return
		}

		err = syncTagsToImage(tx, i)
		ret = i
		return
	})
	return
}

func (db *db) updateImage(i Image) (err error) {
	err = withTx(db, func(tx *sqlx.Tx) (err error) {
		_, err = tx.NamedExec(`UPDATE image SET
                      interpretdate = :interpretdate,
                      processlog = :processlog
                      WHERE image.id = :id`, i)
		if err != nil {
			return
		}

		_, err = tx.NamedExec(`UPDATE imgtext SET
                      text = :text,
                      comment = :comment
                      WHERE rowid = :id`, i)
		if err != nil {
			return
		}

		err = syncTagsToImage(tx, i)
		return
	})
	return
}

func (db *db) deleteImage(s Image) (err error) {
	err = withTx(db, func(tx *sqlx.Tx) (err error) {
		_, err = tx.Exec(`DELETE FROM imgtag WHERE imgid IN
                                  (SELECT id FROM image WHERE image.checksum = $1)`, s.Checksum)
		if err != nil {
			return
		}
		_, err = tx.Exec(`DELETE FROM imgtext WHERE rowid IN
                                  (SELECT id FROM image WHERE image.checksum = $1)`, s.Checksum)
		if err != nil {
			return
		}
		_, err = tx.Exec(`DELETE FROM image WHERE image.checksum = $1`, s.Checksum)
		return
	})
	return
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"net/http"

	"github.com/gin-gonic/gin"
	"gitlab-odx.oracle.com/odx/functions/api"
	"gitlab-odx.oracle.com/odx/functions/api/models"
)

func (s *Server) handleCallList(c *gin.Context) {
	ctx := c.MustGet("ctx").(context.Context)

	appName, ok := c.MustGet(api.AppName).(string)
	if ok && appName == "" {
		c.JSON(http.StatusBadRequest, models.ErrRoutesValidationMissingAppName)
		return
	}
	appRoute, ok := c.MustGet(api.Path).(string)
	if ok && appRoute == "" {
		c.JSON(http.StatusBadRequest, models.ErrRoutesValidationMissingPath)
		return
	}

	filter := models.CallFilter{AppName:appName, Path:appRoute}

	calls, err := s.Datastore.GetTasks(ctx, &filter)
	if err != nil {
		handleErrorResponse(c, err)
		return
	}

	c.JSON(http.StatusOK, fnCallsResponse{"Successfully listed calls", calls})
}
<commit_msg>Add app and route validation to calls API handler<commit_after>package server

import (
	"context"
	"net/http"

	"github.com/gin-gonic/gin"
	"gitlab-odx.oracle.com/odx/functions/api"
	"gitlab-odx.oracle.com/odx/functions/api/models"
)

func (s *Server) handleCallList(c *gin.Context) {
	ctx := c.MustGet("ctx").(context.Context)

	appName, ok := c.MustGet(api.AppName).(string)
	if ok && appName == "" {
		c.JSON(http.StatusBadRequest, models.ErrRoutesValidationMissingAppName)
		return
	}

	_, err := s.Datastore.GetApp(c, appName)
	if err != nil {
		c.JSON(http.StatusNotFound, models.ErrAppsNotFound)
		return
	}

	appRoute, ok := c.MustGet(api.Path).(string)
	if ok && appRoute == "" {
		c.JSON(http.StatusBadRequest, models.ErrRoutesValidationMissingPath)
		return
	}
	_, err = s.Datastore.GetRoute(c, appName, appRoute)
	if err != nil {
		c.JSON(http.StatusNotFound, models.ErrRoutesNotFound)
		return
	}

	filter := models.CallFilter{AppName:appName, Path:appRoute}

	calls, err := s.Datastore.GetTasks(ctx, &filter)
	if err != nil {
		handleErrorResponse(c, err)
		return
	}

	c.JSON(http.StatusOK, fnCallsResponse{"Successfully listed calls", calls})
}
<|endoftext|>


<commit_before>// mod is the entry point to all of the etcd modules.
package mod

import (
	"net/http"

	"github.com/coreos/etcd/mod/dashboard"
	"github.com/gorilla/mux"
)

var ServeMux *http.Handler

func HttpHandler() (handler http.Handler) {
	modMux := mux.NewRouter()
	modMux.PathPrefix("/dashboard/").
		Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler()))
	return modMux
}
<commit_msg>fix(mod): redirect dashboard to /dashboard<commit_after>// mod is the entry point to all of the etcd modules.
package mod

import (
	"net/http"
	"path"

	"github.com/coreos/etcd/mod/dashboard"
	"github.com/gorilla/mux"
)

var ServeMux *http.Handler

func addSlash(w http.ResponseWriter, req *http.Request) {
	http.Redirect(w, req, path.Join("mod", req.URL.Path) + "/", 302)
	return
}

func HttpHandler() (handler http.Handler) {
	modMux := mux.NewRouter()
	modMux.HandleFunc("/dashboard", addSlash)
	modMux.PathPrefix("/dashboard/").
		Handler(http.StripPrefix("/dashboard/", dashboard.HttpHandler()))

	return modMux
}
<|endoftext|>


<commit_before>package worker

import (
	"encoding/json"
	"fmt"
	"github.com/go-redis/redis"
	"github.com/stitchfix/flotilla-os/clients/metrics"
	"github.com/stitchfix/flotilla-os/queue"
	"io/ioutil"
	"math/rand"
	"net/http"
	"strings"
	"time"
	"gopkg.in/tomb.v2"
	"github.com/pkg/errors"
	"github.com/stitchfix/flotilla-os/config"
	"github.com/stitchfix/flotilla-os/execution/engine"
	flotillaLog "github.com/stitchfix/flotilla-os/log"
	"github.com/stitchfix/flotilla-os/state"
)

type statusWorker struct {
	sm                       state.Manager
	ee                       engine.Engine
	conf                     config.Config
	log                      flotillaLog.Logger
	pollInterval             time.Duration
	t                        tomb.Tomb
	engine                   *string
	redisClient              *redis.Client
	workerId                 string
	exceptionExtractorClient *http.Client
	exceptionExtractorUrl    string
}

func (sw *statusWorker) Initialize(conf config.Config, sm state.Manager, ee engine.Engine, log flotillaLog.Logger, pollInterval time.Duration, engine *string, qm queue.Manager) error {
	sw.pollInterval = pollInterval
	sw.conf = conf
	sw.sm = sm
	sw.ee = ee
	sw.log = log
	sw.engine = engine
	sw.workerId = fmt.Sprintf("workerid:%d", rand.Int())

	if sw.conf.IsSet("eks.exception_extractor_url") {
		sw.exceptionExtractorClient = &http.Client{
			Timeout: time.Second * 5,
		}
		sw.exceptionExtractorUrl = sw.conf.GetString("eks.exception_extractor_url")
	}
	sw.setupRedisClient(conf)
	_ = sw.log.Log("message", "initialized a status worker", "engine", *engine)
	return nil
}

func (sw *statusWorker) setupRedisClient(conf config.Config) {
	if *sw.engine == state.EKSEngine {
		sw.redisClient = redis.NewClient(&redis.Options{Addr: conf.GetString("redis_address"), DB: conf.GetInt("redis_db")})
	}
}

func (sw *statusWorker) GetTomb() *tomb.Tomb {
	return &sw.t
}

//
// Run updates status of tasks
//
func (sw *statusWorker) Run() error {
	for {
		select {
		case <-sw.t.Dying():
			sw.log.Log("message", "A status worker was terminated")
			return nil
		default:
			if *sw.engine == state.ECSEngine {
				sw.runOnceECS()
				time.Sleep(sw.pollInterval)
			}

			if *sw.engine == state.EKSEngine {
				sw.runOnceEKS()
				time.Sleep(sw.pollInterval)
			}
		}
	}
}

func (sw *statusWorker) runOnceEKS() {
	rl, err := sw.sm.ListRuns(1000, 0, "started_at", "asc", map[string][]string{
		"queued_at_since": {
			time.Now().AddDate(0, 0, -30).Format(time.RFC3339),
		},
		"status": {state.StatusNeedsRetry, state.StatusRunning, state.StatusQueued, state.StatusPending},
	}, nil, []string{state.EKSEngine})

	if err != nil {
		_ = sw.log.Log("message", "unable to receive runs", "error", fmt.Sprintf("%+v", err))
		return
	}
	runs := rl.Runs
	sw.processEKSRuns(runs)
}

func (sw *statusWorker) processEKSRuns(runs []state.Run) {
	var lockedRuns []state.Run
	for _, run := range runs {
		duration := time.Duration(30) * time.Second
		lock := sw.acquireLock(run, "status", duration)
		if lock {
			lockedRuns = append(lockedRuns, run)
		}
	}
	_ = metrics.Increment(metrics.StatusWorkerLockedRuns, []string{sw.workerId}, float64(len(lockedRuns)))
	for _, run := range lockedRuns {
		start := time.Now()
		_ = sw.log.Log("message", "launching go process eks run", "run", run.RunID)
		go sw.processEKSRun(run)
		_ = metrics.Timing(metrics.StatusWorkerProcessEKSRun, time.Since(start), []string{sw.workerId}, 1)
	}
}
func (sw *statusWorker) acquireLock(run state.Run, purpose string, expiration time.Duration) bool {
	start := time.Now()
	set, err := sw.redisClient.SetNX(fmt.Sprintf("%s-%s", run.RunID, purpose), sw.workerId, expiration).Result()
	if err != nil {
		_ = sw.log.Log("message", "unable to set lock", "error", fmt.Sprintf("%+v", err))
		return true
	}
	_ = metrics.Timing(metrics.StatusWorkerAcquireLock, time.Since(start), []string{sw.workerId}, 1)
	return set
}

func (sw *statusWorker) processEKSRun(run state.Run) {
	_ = sw.log.Log("message", "process eks run", "run", run.RunID)
	reloadRun, err := sw.sm.GetRun(run.RunID)
	if err == nil && reloadRun.Status == state.StatusStopped {
		// Run was updated by another worker process.
		return
	}
	start := time.Now()
	updatedRunWithMetrics, _ := sw.ee.FetchPodMetrics(run)
	_ = metrics.Timing(metrics.StatusWorkerFetchPodMetrics, time.Since(start), []string{sw.workerId}, 1)

	start = time.Now()
	updatedRun, err := sw.ee.FetchUpdateStatus(updatedRunWithMetrics)
	if err != nil {
		_ = sw.log.Log("message", "fetch update status", "run", run.RunID, "error", fmt.Sprintf("%+v", err))
	}
	_ = metrics.Timing(metrics.StatusWorkerFetchUpdateStatus, time.Since(start), []string{sw.workerId}, 1)

	if err == nil {
		subRuns, err := sw.sm.ListRuns(1000, 0, "status", "desc", nil, map[string]string{"PARENT_FLOTILLA_RUN_ID": run.RunID}, state.Engines)
		if err == nil && subRuns.Total > 0 {
			var spawnedRuns state.SpawnedRuns
			for _, subRun := range subRuns.Runs {
				spawnedRuns = append(spawnedRuns, state.SpawnedRun{RunID: subRun.RunID})
			}
			updatedRun.SpawnedRuns = &spawnedRuns
		}
	}
	if err != nil {
		message := fmt.Sprintf("%+v", err)
		_ = sw.log.Log("message", "unable to receive eks runs", "error", message)

		minutesInQueue := time.Now().Sub(*run.QueuedAt).Minutes()
		if strings.Contains(message, "not found") && minutesInQueue > float64(30) {
			stoppedAt := time.Now()
			reason := "Job either timed out or not found on the EKS cluster."
			updatedRun.Status = state.StatusStopped
			updatedRun.FinishedAt = &stoppedAt
			updatedRun.ExitReason = &reason
			_, err = sw.sm.UpdateRun(updatedRun.RunID, updatedRun)
		}

	} else {
		if run.Status != updatedRun.Status && (updatedRun.PodName == run.PodName) {
			_ = sw.log.Log("message", "updating eks run status", "pod", updatedRun.PodName, "status", updatedRun.Status, "exit_code", updatedRun.ExitCode)

			if sw.exceptionExtractorClient != nil && updatedRun.ExitCode != nil && *(updatedRun.ExitCode) != int64(0) {
				go sw.extractExceptions(run.RunID)
			}
			_, err = sw.sm.UpdateRun(updatedRun.RunID, updatedRun)
			if err != nil {
				_ = sw.log.Log("message", "unable to save eks runs", "error", fmt.Sprintf("%+v", err))
			}

			if updatedRun.Status == state.StatusStopped {
				//TODO - move to a separate worker.
				//_ = sw.ee.Terminate(run)
			}
		} else {
			if updatedRun.MaxMemoryUsed != run.MaxMemoryUsed ||
				updatedRun.MaxCpuUsed != run.MaxCpuUsed ||
				updatedRun.Cpu != run.Cpu ||
				updatedRun.PodName != run.PodName ||
				updatedRun.Memory != run.Memory ||
				updatedRun.PodEvents != run.PodEvents ||
				updatedRun.SpawnedRuns != run.SpawnedRuns {
				_, err = sw.sm.UpdateRun(updatedRun.RunID, updatedRun)
			}
		}
	}
}

func (sw *statusWorker) extractExceptions(runID string) {
	//Logs maybe delayed before being persisted to S3.
	time.Sleep(60 * time.Second)
	run, err := sw.sm.GetRun(runID)
	if err == nil {
		jobUrl := fmt.Sprintf("%s/extract/%s", sw.exceptionExtractorUrl, run.RunID)
		res, err := sw.exceptionExtractorClient.Get(jobUrl)
		if err == nil && res != nil && res.Body != nil {
			body, err := ioutil.ReadAll(res.Body)
			if body != nil {
				defer res.Body.Close()
				runExceptions := state.RunExceptions{}
				err = json.Unmarshal(body, &runExceptions)
				if err == nil {
					run.RunExceptions = &runExceptions
				}
			}
			_, _ = sw.sm.UpdateRun(run.RunID, run)
		}
	}
}

func (sw *statusWorker) processEKSRunMetrics(run state.Run) {
	updatedRun, err := sw.ee.FetchPodMetrics(run)
	if err == nil {
		if updatedRun.MaxMemoryUsed != run.MaxMemoryUsed ||
			updatedRun.MaxCpuUsed != run.MaxCpuUsed {
			_, err = sw.sm.UpdateRun(updatedRun.RunID, updatedRun)
		}
	}
}

func (sw *statusWorker) runOnceECS() {
	runReceipt, err := sw.ee.PollStatus()

	if err != nil {
		sw.log.Log("message", "unable to receive status message", "error", fmt.Sprintf("%+v", err))
		return
	}

	// Ensure update is in the env required, otherwise, ack without taking action
	update := runReceipt.Run
	if update != nil {
		//
		// Relies on the reserved env var, FLOTILLA_SERVER_MODE to ensure update
		// belongs to -this- mode of Flotilla
		//
		var serverMode string
		if update.Env != nil {
			for _, kv := range *update.Env {
				if kv.Name == "FLOTILLA_SERVER_MODE" {
					serverMode = kv.Value
				}
			}
		}

		shouldProcess := len(serverMode) > 0 && serverMode == sw.conf.GetString("flotilla_mode")
		if shouldProcess {
			run, err := sw.findRun(update.TaskArn)
			if err != nil {
				sw.log.Log("message", "unable to find run to apply update to", "error", fmt.Sprintf("%+v", err))
				return
			}

			_, err = sw.sm.UpdateRun(run.RunID, *update)
			if err != nil {
				sw.log.Log("message", "error applying status update", "run", run.RunID, "error", fmt.Sprintf("%+v", err))
				return
			}

			// emit status update event
			sw.logStatusUpdate(*update)
		}

		if sw.conf.GetString("flotilla_mode") != "dev" {
			_ = sw.log.Log("message", "Acking status update", "arn", update.TaskArn)
		}
		if err = runReceipt.Done(); err != nil {
			sw.log.Log("message", "Acking status update failed", "arn", update.TaskArn, "error", fmt.Sprintf("%+v", err))
		}
	}
}

func (sw *statusWorker) logStatusUpdate(update state.Run) {
	var err error
	var startedAt, finishedAt time.Time
	var env state.EnvList

	if update.StartedAt != nil {
		startedAt = *update.StartedAt
	}

	if update.FinishedAt != nil {
		finishedAt = *update.FinishedAt
	}

	if update.Env != nil {
		env = *update.Env
	}

	if update.ExitCode != nil {
		err = sw.log.Event("eventClassName", "FlotillaTaskStatus",
			"run_id", update.RunID,
			"task_arn", update.TaskArn,
			"definition_id", update.DefinitionID,
			"alias", update.Alias,
			"image", update.Image,
			"cluster_name", update.ClusterName,
			"exit_code", *update.ExitCode,
			"status", update.Status,
			"started_at", startedAt,
			"finished_at", finishedAt,
			"instance_id", update.InstanceID,
			"instance_dns_name", update.InstanceDNSName,
			"group_name", update.GroupName,
			"user", update.User,
			"task_type", update.TaskType,
			"env", env,
			"executable_id", update.ExecutableID,
			"executable_type", update.ExecutableType)
	} else {
		err = sw.log.Event("eventClassName", "FlotillaTaskStatus",
			"run_id", update.RunID,
			"task_arn", update.TaskArn,
			"definition_id", update.DefinitionID,
			"alias", update.Alias,
			"image", update.Image,
			"cluster_name", update.ClusterName,
			"status", update.Status,
			"started_at", startedAt,
			"finished_at", finishedAt,
			"instance_id", update.InstanceID,
			"instance_dns_name", update.InstanceDNSName,
			"group_name", update.GroupName,
			"user", update.User,
			"task_type", update.TaskType,
			"env", env,
			"executable_id", update.ExecutableID,
			"executable_type", update.ExecutableType)
	}

	if err != nil {
		sw.log.Log("message", "Failed to emit status event", "run_id", update.RunID, "error", err.Error())
	}
}

func (sw *statusWorker) findRun(taskArn string) (state.Run, error) {
	var engines []string
	if sw.engine != nil {
		engines = []string{*sw.engine}
	} else {
		engines = nil
	}

	runs, err := sw.sm.ListRuns(1, 0, "started_at", "asc", map[string][]string{
		"task_arn": {taskArn},
	}, nil, engines)
	if err != nil {
		return state.Run{}, errors.Wrapf(err, "problem finding run by task arn [%s]", taskArn)
	}
	if runs.Total > 0 && len(runs.Runs) > 0 {
		return runs.Runs[0], nil
	}
	return state.Run{}, errors.Errorf("no run found for [%s]", taskArn)
}
<commit_msg>update redis lock (#349)<commit_after>package worker

import (
	"encoding/json"
	"fmt"
	"github.com/go-redis/redis"
	"github.com/pkg/errors"
	"github.com/stitchfix/flotilla-os/clients/metrics"
	"github.com/stitchfix/flotilla-os/config"
	"github.com/stitchfix/flotilla-os/execution/engine"
	flotillaLog "github.com/stitchfix/flotilla-os/log"
	"github.com/stitchfix/flotilla-os/queue"
	"github.com/stitchfix/flotilla-os/state"
	"gopkg.in/tomb.v2"
	"io/ioutil"
	"math/rand"
	"net/http"
	"strings"
	"time"
)

type statusWorker struct {
	sm                       state.Manager
	ee                       engine.Engine
	conf                     config.Config
	log                      flotillaLog.Logger
	pollInterval             time.Duration
	t                        tomb.Tomb
	engine                   *string
	redisClient              *redis.Client
	workerId                 string
	exceptionExtractorClient *http.Client
	exceptionExtractorUrl    string
}

func (sw *statusWorker) Initialize(conf config.Config, sm state.Manager, ee engine.Engine, log flotillaLog.Logger, pollInterval time.Duration, engine *string, qm queue.Manager) error {
	sw.pollInterval = pollInterval
	sw.conf = conf
	sw.sm = sm
	sw.ee = ee
	sw.log = log
	sw.engine = engine
	sw.workerId = fmt.Sprintf("workerid:%d", rand.Int())

	if sw.conf.IsSet("eks.exception_extractor_url") {
		sw.exceptionExtractorClient = &http.Client{
			Timeout: time.Second * 5,
		}
		sw.exceptionExtractorUrl = sw.conf.GetString("eks.exception_extractor_url")
	}
	sw.setupRedisClient(conf)
	_ = sw.log.Log("message", "initialized a status worker", "engine", *engine)
	return nil
}

func (sw *statusWorker) setupRedisClient(conf config.Config) {
	if *sw.engine == state.EKSEngine {
		sw.redisClient = redis.NewClient(&redis.Options{Addr: conf.GetString("redis_address"), DB: conf.GetInt("redis_db")})
	}
}

func (sw *statusWorker) GetTomb() *tomb.Tomb {
	return &sw.t
}

//
// Run updates status of tasks
//
func (sw *statusWorker) Run() error {
	for {
		select {
		case <-sw.t.Dying():
			sw.log.Log("message", "A status worker was terminated")
			return nil
		default:
			if *sw.engine == state.ECSEngine {
				sw.runOnceECS()
				time.Sleep(sw.pollInterval)
			}

			if *sw.engine == state.EKSEngine {
				sw.runOnceEKS()
				time.Sleep(sw.pollInterval)
			}
		}
	}
}

func (sw *statusWorker) runOnceEKS() {
	rl, err := sw.sm.ListRuns(1000, 0, "started_at", "asc", map[string][]string{
		"queued_at_since": {
			time.Now().AddDate(0, 0, -30).Format(time.RFC3339),
		},
		"status": {state.StatusNeedsRetry, state.StatusRunning, state.StatusQueued, state.StatusPending},
	}, nil, []string{state.EKSEngine})

	if err != nil {
		_ = sw.log.Log("message", "unable to receive runs", "error", fmt.Sprintf("%+v", err))
		return
	}
	runs := rl.Runs
	sw.processEKSRuns(runs)
}

func (sw *statusWorker) processEKSRuns(runs []state.Run) {
	var lockedRuns []state.Run
	for _, run := range runs {
		duration := time.Duration(30) * time.Second
		lock := sw.acquireLock(run, "status", duration)
		if lock {
			lockedRuns = append(lockedRuns, run)
		}
	}
	_ = metrics.Increment(metrics.StatusWorkerLockedRuns, []string{sw.workerId}, float64(len(lockedRuns)))
	for _, run := range lockedRuns {
		start := time.Now()
		_ = sw.log.Log("message", "launching go process eks run", "run", run.RunID)
		go sw.processEKSRun(run)
		_ = metrics.Timing(metrics.StatusWorkerProcessEKSRun, time.Since(start), []string{sw.workerId}, 1)
	}
}
func (sw *statusWorker) acquireLock(run state.Run, purpose string, expiration time.Duration) bool {
	start := time.Now()
	key := fmt.Sprintf("%s-%s", run.RunID, purpose)
	ttl, err := sw.redisClient.TTL(key).Result()
	if err == nil && ttl.Nanoseconds() < 0 {
		_, err = sw.redisClient.Del(key).Result()
	}
	set, err := sw.redisClient.SetNX(key, sw.workerId, expiration).Result()
	if err != nil {
		_ = sw.log.Log("message", "unable to set lock", "error", fmt.Sprintf("%+v", err))
		return true
	}
	_ = metrics.Timing(metrics.StatusWorkerAcquireLock, time.Since(start), []string{sw.workerId}, 1)
	return set
}

func (sw *statusWorker) processEKSRun(run state.Run) {
	_ = sw.log.Log("message", "process eks run", "run", run.RunID)
	reloadRun, err := sw.sm.GetRun(run.RunID)
	if err == nil && reloadRun.Status == state.StatusStopped {
		// Run was updated by another worker process.
		return
	}
	start := time.Now()
	updatedRunWithMetrics, _ := sw.ee.FetchPodMetrics(run)
	_ = metrics.Timing(metrics.StatusWorkerFetchPodMetrics, time.Since(start), []string{sw.workerId}, 1)

	start = time.Now()
	updatedRun, err := sw.ee.FetchUpdateStatus(updatedRunWithMetrics)
	if err != nil {
		_ = sw.log.Log("message", "fetch update status", "run", run.RunID, "error", fmt.Sprintf("%+v", err))
	}
	_ = metrics.Timing(metrics.StatusWorkerFetchUpdateStatus, time.Since(start), []string{sw.workerId}, 1)

	if err == nil {
		subRuns, err := sw.sm.ListRuns(1000, 0, "status", "desc", nil, map[string]string{"PARENT_FLOTILLA_RUN_ID": run.RunID}, state.Engines)
		if err == nil && subRuns.Total > 0 {
			var spawnedRuns state.SpawnedRuns
			for _, subRun := range subRuns.Runs {
				spawnedRuns = append(spawnedRuns, state.SpawnedRun{RunID: subRun.RunID})
			}
			updatedRun.SpawnedRuns = &spawnedRuns
		}
	}
	if err != nil {
		message := fmt.Sprintf("%+v", err)
		_ = sw.log.Log("message", "unable to receive eks runs", "error", message)

		minutesInQueue := time.Now().Sub(*run.QueuedAt).Minutes()
		if strings.Contains(message, "not found") && minutesInQueue > float64(30) {
			stoppedAt := time.Now()
			reason := "Job either timed out or not found on the EKS cluster."
			updatedRun.Status = state.StatusStopped
			updatedRun.FinishedAt = &stoppedAt
			updatedRun.ExitReason = &reason
			_, err = sw.sm.UpdateRun(updatedRun.RunID, updatedRun)
		}

	} else {
		if run.Status != updatedRun.Status && (updatedRun.PodName == run.PodName) {
			_ = sw.log.Log("message", "updating eks run status", "pod", updatedRun.PodName, "status", updatedRun.Status, "exit_code", updatedRun.ExitCode)

			if sw.exceptionExtractorClient != nil && updatedRun.ExitCode != nil && *(updatedRun.ExitCode) != int64(0) {
				go sw.extractExceptions(run.RunID)
			}
			_, err = sw.sm.UpdateRun(updatedRun.RunID, updatedRun)
			if err != nil {
				_ = sw.log.Log("message", "unable to save eks runs", "error", fmt.Sprintf("%+v", err))
			}

			if updatedRun.Status == state.StatusStopped {
				//TODO - move to a separate worker.
				//_ = sw.ee.Terminate(run)
			}
		} else {
			if updatedRun.MaxMemoryUsed != run.MaxMemoryUsed ||
				updatedRun.MaxCpuUsed != run.MaxCpuUsed ||
				updatedRun.Cpu != run.Cpu ||
				updatedRun.PodName != run.PodName ||
				updatedRun.Memory != run.Memory ||
				updatedRun.PodEvents != run.PodEvents ||
				updatedRun.SpawnedRuns != run.SpawnedRuns {
				_, err = sw.sm.UpdateRun(updatedRun.RunID, updatedRun)
			}
		}
	}
}

func (sw *statusWorker) extractExceptions(runID string) {
	//Logs maybe delayed before being persisted to S3.
	time.Sleep(60 * time.Second)
	run, err := sw.sm.GetRun(runID)
	if err == nil {
		jobUrl := fmt.Sprintf("%s/extract/%s", sw.exceptionExtractorUrl, run.RunID)
		res, err := sw.exceptionExtractorClient.Get(jobUrl)
		if err == nil && res != nil && res.Body != nil {
			body, err := ioutil.ReadAll(res.Body)
			if body != nil {
				defer res.Body.Close()
				runExceptions := state.RunExceptions{}
				err = json.Unmarshal(body, &runExceptions)
				if err == nil {
					run.RunExceptions = &runExceptions
				}
			}
			_, _ = sw.sm.UpdateRun(run.RunID, run)
		}
	}
}

func (sw *statusWorker) processEKSRunMetrics(run state.Run) {
	updatedRun, err := sw.ee.FetchPodMetrics(run)
	if err == nil {
		if updatedRun.MaxMemoryUsed != run.MaxMemoryUsed ||
			updatedRun.MaxCpuUsed != run.MaxCpuUsed {
			_, err = sw.sm.UpdateRun(updatedRun.RunID, updatedRun)
		}
	}
}

func (sw *statusWorker) runOnceECS() {
	runReceipt, err := sw.ee.PollStatus()

	if err != nil {
		sw.log.Log("message", "unable to receive status message", "error", fmt.Sprintf("%+v", err))
		return
	}

	// Ensure update is in the env required, otherwise, ack without taking action
	update := runReceipt.Run
	if update != nil {
		//
		// Relies on the reserved env var, FLOTILLA_SERVER_MODE to ensure update
		// belongs to -this- mode of Flotilla
		//
		var serverMode string
		if update.Env != nil {
			for _, kv := range *update.Env {
				if kv.Name == "FLOTILLA_SERVER_MODE" {
					serverMode = kv.Value
				}
			}
		}

		shouldProcess := len(serverMode) > 0 && serverMode == sw.conf.GetString("flotilla_mode")
		if shouldProcess {
			run, err := sw.findRun(update.TaskArn)
			if err != nil {
				sw.log.Log("message", "unable to find run to apply update to", "error", fmt.Sprintf("%+v", err))
				return
			}

			_, err = sw.sm.UpdateRun(run.RunID, *update)
			if err != nil {
				sw.log.Log("message", "error applying status update", "run", run.RunID, "error", fmt.Sprintf("%+v", err))
				return
			}

			// emit status update event
			sw.logStatusUpdate(*update)
		}

		if sw.conf.GetString("flotilla_mode") != "dev" {
			_ = sw.log.Log("message", "Acking status update", "arn", update.TaskArn)
		}
		if err = runReceipt.Done(); err != nil {
			sw.log.Log("message", "Acking status update failed", "arn", update.TaskArn, "error", fmt.Sprintf("%+v", err))
		}
	}
}

func (sw *statusWorker) logStatusUpdate(update state.Run) {
	var err error
	var startedAt, finishedAt time.Time
	var env state.EnvList

	if update.StartedAt != nil {
		startedAt = *update.StartedAt
	}

	if update.FinishedAt != nil {
		finishedAt = *update.FinishedAt
	}

	if update.Env != nil {
		env = *update.Env
	}

	if update.ExitCode != nil {
		err = sw.log.Event("eventClassName", "FlotillaTaskStatus",
			"run_id", update.RunID,
			"task_arn", update.TaskArn,
			"definition_id", update.DefinitionID,
			"alias", update.Alias,
			"image", update.Image,
			"cluster_name", update.ClusterName,
			"exit_code", *update.ExitCode,
			"status", update.Status,
			"started_at", startedAt,
			"finished_at", finishedAt,
			"instance_id", update.InstanceID,
			"instance_dns_name", update.InstanceDNSName,
			"group_name", update.GroupName,
			"user", update.User,
			"task_type", update.TaskType,
			"env", env,
			"executable_id", update.ExecutableID,
			"executable_type", update.ExecutableType)
	} else {
		err = sw.log.Event("eventClassName", "FlotillaTaskStatus",
			"run_id", update.RunID,
			"task_arn", update.TaskArn,
			"definition_id", update.DefinitionID,
			"alias", update.Alias,
			"image", update.Image,
			"cluster_name", update.ClusterName,
			"status", update.Status,
			"started_at", startedAt,
			"finished_at", finishedAt,
			"instance_id", update.InstanceID,
			"instance_dns_name", update.InstanceDNSName,
			"group_name", update.GroupName,
			"user", update.User,
			"task_type", update.TaskType,
			"env", env,
			"executable_id", update.ExecutableID,
			"executable_type", update.ExecutableType)
	}

	if err != nil {
		sw.log.Log("message", "Failed to emit status event", "run_id", update.RunID, "error", err.Error())
	}
}

func (sw *statusWorker) findRun(taskArn string) (state.Run, error) {
	var engines []string
	if sw.engine != nil {
		engines = []string{*sw.engine}
	} else {
		engines = nil
	}

	runs, err := sw.sm.ListRuns(1, 0, "started_at", "asc", map[string][]string{
		"task_arn": {taskArn},
	}, nil, engines)
	if err != nil {
		return state.Run{}, errors.Wrapf(err, "problem finding run by task arn [%s]", taskArn)
	}
	if runs.Total > 0 && len(runs.Runs) > 0 {
		return runs.Runs[0], nil
	}
	return state.Run{}, errors.Errorf("no run found for [%s]", taskArn)
}
<|endoftext|>


<commit_before>package integration_test

import (
	"path/filepath"

	"github.com/cloudfoundry/libbuildpack/cutlass"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("running supply buildpacks before the binary buildpack", func() {
	var app *cutlass.App
	AfterEach(func() {
		if app != nil {
			app.Destroy()
		}
		app = nil
	})

	Context("the app is pushed once", func() {
		BeforeEach(func() {
			if version, err := cutlass.ApiVersion(); err != nil || version == "2.65.0" {
				Skip("API version does not have multi-buildpack support")
			}

			app = cutlass.New(filepath.Join(bpDir, "fixtures", "fake_supply_binary_app"))
			app.Buildpacks = []string{
				"https://buildpacks.cloudfoundry.org/fixtures/new_supply_dotnet.zip",
				"binary_buildpack",
			}
			app.Disk = "1G"
		})

		It("finds the supplied dependency in the runtime container", func() {
			PushAppAndConfirm(app)

			Expect(app.Stdout.String()).To(ContainSubstring("SUPPLYING DOTNET"))
			Expect(app.GetBody("/")).To(ContainSubstring("dotnet: 1.0.1"))
		})
	})
})
<commit_msg>Fix multi-buildpack dotnet check [#149434941]<commit_after>package integration_test

import (
	"path/filepath"

	"github.com/cloudfoundry/libbuildpack/cutlass"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("running supply buildpacks before the binary buildpack", func() {
	var app *cutlass.App
	AfterEach(func() {
		if app != nil {
			app.Destroy()
		}
		app = nil
	})

	Context("the app is pushed once", func() {
		BeforeEach(func() {
			if version, err := cutlass.ApiVersion(); err != nil || version == "2.65.0" {
				Skip("API version does not have multi-buildpack support")
			}

			app = cutlass.New(filepath.Join(bpDir, "fixtures", "fake_supply_binary_app"))
			app.Buildpacks = []string{
				"https://github.com/cloudfoundry/dotnet-core-buildpack#develop",
				"binary_buildpack",
			}
			app.Disk = "1G"
		})

		It("finds the supplied dependency in the runtime container", func() {
			PushAppAndConfirm(app)

			Expect(app.Stdout.String()).To(ContainSubstring("Supplying Dotnet Core"))
			Expect(app.GetBody("/")).To(MatchRegexp(`dotnet: \d+\.\d+\.\d+`))
		})
	})
})
<|endoftext|>


<commit_before>package controllers

import (
	"github.com/codeb2cc/gomemcache/memcache"
	"github.com/revel/revel"
	"strings"
)

var (
	mcServers []string
	mcClients map[string]*memcache.Client
)

func init() {
	revel.OnAppStart(func() {
		mcServers = strings.Split(revel.Config.StringDefault("memcached", "127.0.0.1:11211"), "|")
		mcClients = make(map[string]*memcache.Client)
		for _, server := range mcServers {
			mcClients[server] = memcache.New(server)
		}
	})
}
<commit_msg>Add timeout config for memcached connections<commit_after>package controllers

import (
	"github.com/codeb2cc/gomemcache/memcache"
	"github.com/revel/revel"
	"strings"
	"time"
)

var (
	mcServers []string
	mcClients map[string]*memcache.Client
)

func init() {
	revel.OnAppStart(func() {
		mcServers = strings.Split(revel.Config.StringDefault("memcached", "127.0.0.1:11211"), "|")
		mcClients = make(map[string]*memcache.Client)
		for _, server := range mcServers {
			mcClients[server] = memcache.New(server)
			mcClients[server].Timeout = time.Duration(1000) * time.Millisecond
		}
	})
}
<|endoftext|>


<commit_before>// Copyright 2013 Bobby Powers. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package boosd

import (
	"bytes"
	"fmt"
	"github.com/bpowers/boosd/runtime"
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"strconv"
	"text/template"
)

const modelTmpl = `
package main

import (
	"github.com/bpowers/boosd/runtime"
)

var (
	mdlMainName = "main"
	mdlMainVars = map[string]runtime.Var{
{{range $.Vars}}
		"{{.Name}}": runtime.Var{"{{.Name}}", runtime.{{.Type}}},
{{end}}
	}
)

type simMain struct {
	runtime.BaseSim
}

type mdlMain struct {
	runtime.BaseModel
}

func simMainStep(s *runtime.BaseSim, dt float64) {
{{range $.Equations}}
	{{.}}
{{end}}
{{range $.Stocks}}
	{{.}}
{{end}}
}

func (m *mdlMain) NewSim() runtime.Sim {
	ts := runtime.Timespec{
		Start:    {{$.Time.Start}},
		End:      {{$.Time.End}},
		DT:       {{$.Time.DT}},
		SaveStep: {{$.Time.SaveStep}},
	}
	tables := map[string]runtime.Table{}
	consts := runtime.Data{}

	s := new(simMain)
	s.Init(m, ts, tables, consts)
	s.Step = simMainStep

{{range $.Initials}}
	{{.}}
{{end}}
	s.Curr["time"] = ts.Start

	runtime.RegisterSim("main", s)

	return s
}

func init() {
	m := &mdlMain{
		runtime.BaseModel{
			MName: mdlMainName,
			Vars:  mdlMainVars,
		},
	}

	runtime.RegisterModel(m)
}

func main() {
	runtime.Main()
}
`

type generator struct {
	Vars      map[string]runtime.Var
	Time      runtime.Timespec
	Equations []string
	Stocks    []string
	Initials  []string
}

func (g *generator) declList(list []Decl) {
}

// constEval returns the float64 value represented by Expr, or an
// error if it can't be evaluated at compile time.
func constEval(e Expr) (v float64, err error) {
	val, ok := e.(*UnitExpr)
	if !ok {
		err = fmt.Errorf("timespec val %T not UnitExpr", e)
		return
	}
	basic, ok := val.X.(*BasicLit)
	if !ok {
		err = fmt.Errorf("timespec val %T not BasicLit", val.X)
		return
	}
	return strconv.ParseFloat(basic.Value, 64)
}

func isConst(e Expr) bool {
	if _, err := constEval(e); err == nil {
		return true
	}
	return false
}

func kvConvert(e Expr) (k string, v Expr, err error) {
	kv, ok := e.(*KeyValueExpr)
	if !ok {
		err = fmt.Errorf("e %T not KVeyValueExpr: %v", e, e)
		return
	}
	ident, ok := kv.Key.(*Ident)
	if !ok {
		err = fmt.Errorf("e key %T not Ident: %v", kv.Key, kv.Key)
		return
	}
	return ident.Name, kv.Value, nil
}

func (g *generator) timespec(elts []Expr) {
	for _, e := range elts {
		k, val, err := kvConvert(e)
		if err != nil {
			panic(fmt.Sprintf("kvConvert(%v): %s", e, err))
		}
		v, err := constEval(val)
		if err != nil {
			panic(fmt.Sprintf("timespec constEval(%v): %s",
				val, err))
		}
		switch k {
		case "start":
			g.Time.Start = v
		case "end":
			g.Time.End = v
		case "dt":
			g.Time.DT = v
		case "save_step":
			g.Time.SaveStep = v
		default:
			panic(fmt.Sprintf("timespec unknown key %s", k))
		}
	}
}

var identAux = &Ident{Name: "aux"}

func varFromDecl(d *VarDecl) (v runtime.Var, err error) {
	if d.Type == nil {
		d.Type = identAux
	}
	return runtime.Var{d.Name.Name, runtime.TypeForName(d.Type.Name)}, nil
}

func (g *generator) initial(name string, expr Expr) {
	val, err := constEval(expr)
	if err != nil {
		panic(fmt.Sprintf("initial(%s): non-const %v", name, expr))
	}
	init := fmt.Sprintf(`s.Curr["%s"] = %f`, name, val)
	g.Initials = append(g.Initials, init)
}

func (g *generator) stock(name string, expr Expr) {
	cl, ok := expr.(*CompositeLit)
	if !ok {
		panic(fmt.Sprintf("stock is %T, not CompositeLit", expr))
	}
	var in, out string
	for _, e := range cl.Elts {
		k, val, err := kvConvert(e)
		if err != nil {
			panic(fmt.Sprintf("kvConvert(%s): %s", name, err))
		}
		switch k {
		case "initial":
			g.initial(name, val)
		case "inflow":
			in = fmt.Sprintf("%s", val)
		case "outflow":
			out = fmt.Sprintf("-(%s)", val)
		default:
			panic(fmt.Sprintf("stock(%s): unknown k %s",
				name, k))
		}
	}
	eqn := fmt.Sprintf(`s.Next["%s"] = s.Curr["%s"] + (%s %s)*dt`, name, name, in, out)
	g.Stocks = append(g.Stocks, eqn)
}

func (g *generator) expr(name string, expr Expr) {
	if g.Vars[name].Type == runtime.TyAux {
		if isConst(expr) {
			g.initial(name, expr)
			eqn := fmt.Sprintf(`s.Next["%s"] = %s`, name, expr)
			g.Stocks = append(g.Stocks, eqn)
		} else {
			eqn := fmt.Sprintf(`s.Curr["%s"] = %s`, name, expr)
			g.Equations = append(g.Equations, eqn)
		}
	} else {
		eqn := fmt.Sprintf(`s.Curr["%s"] = %s`, name, expr)
		g.Equations = append(g.Equations, eqn)
	}
}

func (g *generator) assign(s *AssignStmt) {
	if s.Lhs.Name.Name == "timespec" {
		c, ok := s.Rhs.(*CompositeLit)
		if !ok {
			panic(fmt.Sprintf("timespec is %T, not CompositeLit",
				s.Rhs))
		}
		g.timespec(c.Elts)
		return
	}
	v, err := varFromDecl(s.Lhs)
	if err != nil {
		panic(fmt.Sprintf("varFromDecl(%v): %s", s.Lhs, err))
	}
	g.Vars[v.Name] = v
	if v.Type == runtime.TyStock {
		g.stock(v.Name, s.Rhs)
	} else {
		g.expr(v.Name, s.Rhs)
	}
}

func (g *generator) stmt(s Stmt) {
	switch ss := s.(type) {
	case *AssignStmt:
		g.assign(ss)
	default:
		log.Printf("stmt(%T): unimplemented - %v", s, s)
	}
}

func (g *generator) model(m *ModelDecl) {
	if m.Name.Name != "main" {
		log.Printf("non-main model not supported: %v", m)
		return
	}
	for _, s := range m.Body.List {
		g.stmt(s)
	}
}

func (g *generator) file(f *File) []byte {
	for _, d := range f.Decls {
		md, ok := d.(*ModelDecl)
		if !ok {
			log.Printf("top level decl that isn't a model: %v (%T)", d, d)
			continue
		}
		g.model(md)
	}

	var buf bytes.Buffer
	tmpl := template.New("model.go")
	if _, err := tmpl.Parse(modelTmpl); err != nil {
		panic(fmt.Sprintf("Parse(modelTmpl): %s", err))
	}
	if err := tmpl.Execute(&buf, g); err != nil {
		panic(fmt.Sprintf("Execute(%v): %s", g, err))
	}

	return buf.Bytes()
}

func GenGo(f *File) (*ast.File, error) {
	g := new(generator)
	g.Vars = map[string]runtime.Var{}
	g.Equations = []string{}
	g.Stocks = []string{}
	g.Initials = []string{}
	code := g.file(f)

	fset := token.NewFileSet()
	goFile, err := parser.ParseFile(fset, "model.go", code, 0)
	if err != nil {
		return nil, err
	}

	return goFile, nil
}
<commit_msg>lib: generate: clean up template & log temp model output<commit_after>// Copyright 2013 Bobby Powers. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package boosd

import (
	"bytes"
	"fmt"
	"github.com/bpowers/boosd/runtime"
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"strconv"
	"text/template"
)

const modelTmpl = `
package main

import (
	"github.com/bpowers/boosd/runtime"
)

var (
	mdlMainName = "main"
	mdlMainVars = map[string]runtime.Var{
{{range $.Vars}}
		"{{.Name}}": runtime.Var{"{{.Name}}", runtime.{{.Type}}},{{end}}
	}
)

type simMain struct {
	runtime.BaseSim
}

type mdlMain struct {
	runtime.BaseModel
}

func simMainStep(s *runtime.BaseSim, dt float64) {
{{range $.Equations}}
	{{.}}
{{end}}
{{range $.Stocks}}
	{{.}}
{{end}}
}

func (m *mdlMain) NewSim() runtime.Sim {
	ts := runtime.Timespec{
		Start:    {{$.Time.Start}},
		End:      {{$.Time.End}},
		DT:       {{$.Time.DT}},
		SaveStep: {{$.Time.SaveStep}},
	}
	tables := map[string]runtime.Table{}
	consts := runtime.Data{}

	s := new(simMain)
	s.Init(m, ts, tables, consts)
	s.Step = simMainStep

{{range $.Initials}}
	{{.}}
{{end}}
	s.Curr["time"] = ts.Start

	runtime.RegisterSim("main", s)

	return s
}

func init() {
	m := &mdlMain{
		runtime.BaseModel{
			MName: mdlMainName,
			Vars:  mdlMainVars,
		},
	}

	runtime.RegisterModel(m)
}

func main() {
	runtime.Main()
}
`

type generator struct {
	Vars      map[string]runtime.Var
	Time      runtime.Timespec
	Equations []string
	Stocks    []string
	Initials  []string
}

func (g *generator) declList(list []Decl) {
}

// constEval returns the float64 value represented by Expr, or an
// error if it can't be evaluated at compile time.
func constEval(e Expr) (v float64, err error) {
	val, ok := e.(*UnitExpr)
	if !ok {
		err = fmt.Errorf("timespec val %T not UnitExpr", e)
		return
	}
	basic, ok := val.X.(*BasicLit)
	if !ok {
		err = fmt.Errorf("timespec val %T not BasicLit", val.X)
		return
	}
	return strconv.ParseFloat(basic.Value, 64)
}

func isConst(e Expr) bool {
	if _, err := constEval(e); err == nil {
		return true
	}
	return false
}

func kvConvert(e Expr) (k string, v Expr, err error) {
	kv, ok := e.(*KeyValueExpr)
	if !ok {
		err = fmt.Errorf("e %T not KVeyValueExpr: %v", e, e)
		return
	}
	ident, ok := kv.Key.(*Ident)
	if !ok {
		err = fmt.Errorf("e key %T not Ident: %v", kv.Key, kv.Key)
		return
	}
	return ident.Name, kv.Value, nil
}

func (g *generator) timespec(elts []Expr) {
	for _, e := range elts {
		k, val, err := kvConvert(e)
		if err != nil {
			panic(fmt.Sprintf("kvConvert(%v): %s", e, err))
		}
		v, err := constEval(val)
		if err != nil {
			panic(fmt.Sprintf("timespec constEval(%v): %s",
				val, err))
		}
		switch k {
		case "start":
			g.Time.Start = v
		case "end":
			g.Time.End = v
		case "dt":
			g.Time.DT = v
		case "save_step":
			g.Time.SaveStep = v
		default:
			panic(fmt.Sprintf("timespec unknown key %s", k))
		}
	}
}

var identAux = &Ident{Name: "aux"}

func varFromDecl(d *VarDecl) (v runtime.Var, err error) {
	if d.Type == nil {
		d.Type = identAux
	}
	return runtime.Var{d.Name.Name, runtime.TypeForName(d.Type.Name)}, nil
}

func (g *generator) initial(name string, expr Expr) {
	val, err := constEval(expr)
	if err != nil {
		panic(fmt.Sprintf("initial(%s): non-const %v", name, expr))
	}
	init := fmt.Sprintf(`s.Curr["%s"] = %f`, name, val)
	g.Initials = append(g.Initials, init)
}

func (g *generator) stock(name string, expr Expr) {
	cl, ok := expr.(*CompositeLit)
	if !ok {
		panic(fmt.Sprintf("stock is %T, not CompositeLit", expr))
	}
	var in, out string
	for _, e := range cl.Elts {
		k, val, err := kvConvert(e)
		if err != nil {
			panic(fmt.Sprintf("kvConvert(%s): %s", name, err))
		}
		switch k {
		case "initial":
			g.initial(name, val)
		case "inflow":
			in = fmt.Sprintf("%s", val)
		case "outflow":
			out = fmt.Sprintf("-(%s)", val)
		default:
			panic(fmt.Sprintf("stock(%s): unknown k %s",
				name, k))
		}
	}
	eqn := fmt.Sprintf(`s.Next["%s"] = s.Curr["%s"] + (%s %s)*dt`, name, name, in, out)
	g.Stocks = append(g.Stocks, eqn)
}

func (g *generator) expr(name string, expr Expr) {
	if g.Vars[name].Type == runtime.TyAux {
		if isConst(expr) {
			g.initial(name, expr)
			eqn := fmt.Sprintf(`s.Next["%s"] = %s`, name, expr)
			g.Stocks = append(g.Stocks, eqn)
		} else {
			eqn := fmt.Sprintf(`s.Curr["%s"] = %s`, name, expr)
			g.Equations = append(g.Equations, eqn)
		}
	} else {
		eqn := fmt.Sprintf(`s.Curr["%s"] = %s`, name, expr)
		g.Equations = append(g.Equations, eqn)
	}
}

func (g *generator) assign(s *AssignStmt) {
	if s.Lhs.Name.Name == "timespec" {
		c, ok := s.Rhs.(*CompositeLit)
		if !ok {
			panic(fmt.Sprintf("timespec is %T, not CompositeLit",
				s.Rhs))
		}
		g.timespec(c.Elts)
		return
	}
	v, err := varFromDecl(s.Lhs)
	if err != nil {
		panic(fmt.Sprintf("varFromDecl(%v): %s", s.Lhs, err))
	}
	g.Vars[v.Name] = v
	if v.Type == runtime.TyStock {
		g.stock(v.Name, s.Rhs)
	} else {
		g.expr(v.Name, s.Rhs)
	}
}

func (g *generator) stmt(s Stmt) {
	switch ss := s.(type) {
	case *AssignStmt:
		g.assign(ss)
	default:
		log.Printf("stmt(%T): unimplemented - %v", s, s)
	}
}

func (g *generator) model(m *ModelDecl) {
	if m.Name.Name != "main" {
		log.Printf("non-main model not supported: %v", m)
		return
	}
	for _, s := range m.Body.List {
		g.stmt(s)
	}
}

func (g *generator) file(f *File) []byte {
	for _, d := range f.Decls {
		md, ok := d.(*ModelDecl)
		if !ok {
			log.Printf("top level decl that isn't a model: %v (%T)", d, d)
			continue
		}
		g.model(md)
	}

	var buf bytes.Buffer
	tmpl := template.New("model.go")
	if _, err := tmpl.Parse(modelTmpl); err != nil {
		panic(fmt.Sprintf("Parse(modelTmpl): %s", err))
	}
	if err := tmpl.Execute(&buf, g); err != nil {
		panic(fmt.Sprintf("Execute(%v): %s", g, err))
	}

	return buf.Bytes()
}

func GenGo(f *File) (*ast.File, error) {
	g := new(generator)
	g.Vars = map[string]runtime.Var{}
	g.Equations = []string{}
	g.Stocks = []string{}
	g.Initials = []string{}
	code := g.file(f)
	log.Printf("c: %s", code)

	fset := token.NewFileSet()
	goFile, err := parser.ParseFile(fset, "model.go", code, 0)
	if err != nil {
		return nil, err
	}

	return goFile, nil
}
<|endoftext|>


<commit_before>package jsonapi

import (
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestParseBodyInvalidDocument(t *testing.T) {
	readers := []io.Reader{
		stringReader(``),
		stringReader(`1`),
		stringReader(`"foo"`),
		stringReader(`true`),
		stringReader(`[]`),
	}

	for _, r := range readers {
		doc, err := ParseBody(r)
		assert.Error(t, err)
		assert.Nil(t, doc)
	}
}

func TestParseBodyEmptyDocument(t *testing.T) {
	doc, err := ParseBody(stringReader(`{}`))
	assert.Error(t, err)
	assert.Nil(t, doc)
}

func TestParseBodyMinimumSingleDocument(t *testing.T) {
	doc, err := ParseBody(stringReader(`{
  		"data": {
    		"type": "foo"
		}
	}`))
	assert.NoError(t, err)
	assert.Equal(t, &Document{
		Data: &HybridResource{
			One: &Resource{
				Type: "foo",
			},
		},
	}, doc)
}

func TestParseBodySingleDocument(t *testing.T) {
	doc, err := ParseBody(stringReader(`{
  		"data": {
    		"type": "foo",
    		"id": "1",
    		"attributes": {},
    		"relationships": {}
		}
	}`))
	assert.NoError(t, err)
	assert.Equal(t, &Document{
		Data: &HybridResource{
			One: &Resource{
				Type:          "foo",
				ID:            "1",
				Attributes:    make(Map),
				Relationships: make(map[string]HybridDocument),
			},
		},
	}, doc)
}

func TestWriteResponseMinimumSingleDocument(t *testing.T) {
	writer := httptest.NewRecorder()
	err := WriteResponse(writer, http.StatusOK, &Document{
		Data: &HybridResource{
			One: &Resource{
				Type: "foo",
			},
		},
	})
	assert.NoError(t, err)
	assert.JSONEq(t, `{
  		"data": {
    		"type": "foo"
		}
	}`, writer.Body.String())
}

func TestWriteResponseSingleDocument(t *testing.T) {
	writer := httptest.NewRecorder()
	err := WriteResponse(writer, http.StatusOK, &Document{
		Data: &HybridResource{
			One: &Resource{
				Type: "foo",
				ID:   "1",
			},
		},
	})
	assert.NoError(t, err)
	assert.JSONEq(t, `{
  		"data": {
    		"type": "foo",
    		"id": "1"
		}
	}`, writer.Body.String())
}

func BenchmarkParseBody(b *testing.B) {
	reader := stringReader(`{
		"links": {
			"self": "http://0.0.0.0:1234/api/foo/1"
		},
		"data": {
			"type": "foo",
			"id": "1",
			"attributes": {
				"foo": "bar",
				"bar": "baz"
			}
		}
	}`)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_, err := ParseBody(reader)
		if err != nil {
			panic(err)
		}

		reader.Seek(0, io.SeekStart)
	}
}

func BenchmarkWriteResponse(b *testing.B) {
	doc := &Document{
		Links: &DocumentLinks{
			Self: "http://0.0.0.0:1234/api/foo/1",
		},
		Data: &HybridResource{
			One: &Resource{
				Type: "foo",
				ID:   "1",
				Attributes: Map{
					"foo": "bar",
					"bar": "baz",
				},
			},
		},
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		err := WriteResponse(httptest.NewRecorder(), http.StatusOK, doc)
		if err != nil {
			panic(err)
		}
	}
}
<commit_msg>test parse body with errors<commit_after>package jsonapi

import (
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestParseBodyInvalidDocument(t *testing.T) {
	readers := []io.Reader{
		stringReader(``),
		stringReader(`1`),
		stringReader(`"foo"`),
		stringReader(`true`),
		stringReader(`[]`),
	}

	for _, r := range readers {
		doc, err := ParseBody(r)
		assert.Error(t, err)
		assert.Nil(t, doc)
	}
}

func TestParseBodyDocumentWithErrors(t *testing.T) {
	doc, err := ParseBody(stringReader(`{
		"errors": [{
			"status": "404"
		}]
	}`))
	assert.Error(t, err)
	assert.Nil(t, doc)
}

func TestParseBodyEmptyDocument(t *testing.T) {
	doc, err := ParseBody(stringReader(`{}`))
	assert.Error(t, err)
	assert.Nil(t, doc)
}

func TestParseBodyMinimumSingleDocument(t *testing.T) {
	doc, err := ParseBody(stringReader(`{
  		"data": {
    		"type": "foo"
		}
	}`))
	assert.NoError(t, err)
	assert.Equal(t, &Document{
		Data: &HybridResource{
			One: &Resource{
				Type: "foo",
			},
		},
	}, doc)
}

func TestParseBodySingleDocument(t *testing.T) {
	doc, err := ParseBody(stringReader(`{
  		"data": {
    		"type": "foo",
    		"id": "1",
    		"attributes": {},
    		"relationships": {}
		}
	}`))
	assert.NoError(t, err)
	assert.Equal(t, &Document{
		Data: &HybridResource{
			One: &Resource{
				Type:          "foo",
				ID:            "1",
				Attributes:    make(Map),
				Relationships: make(map[string]HybridDocument),
			},
		},
	}, doc)
}

func TestWriteResponseMinimumSingleDocument(t *testing.T) {
	writer := httptest.NewRecorder()
	err := WriteResponse(writer, http.StatusOK, &Document{
		Data: &HybridResource{
			One: &Resource{
				Type: "foo",
			},
		},
	})
	assert.NoError(t, err)
	assert.JSONEq(t, `{
  		"data": {
    		"type": "foo"
		}
	}`, writer.Body.String())
}

func TestWriteResponseSingleDocument(t *testing.T) {
	writer := httptest.NewRecorder()
	err := WriteResponse(writer, http.StatusOK, &Document{
		Data: &HybridResource{
			One: &Resource{
				Type: "foo",
				ID:   "1",
			},
		},
	})
	assert.NoError(t, err)
	assert.JSONEq(t, `{
  		"data": {
    		"type": "foo",
    		"id": "1"
		}
	}`, writer.Body.String())
}

func BenchmarkParseBody(b *testing.B) {
	reader := stringReader(`{
		"links": {
			"self": "http://0.0.0.0:1234/api/foo/1"
		},
		"data": {
			"type": "foo",
			"id": "1",
			"attributes": {
				"foo": "bar",
				"bar": "baz"
			}
		}
	}`)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		_, err := ParseBody(reader)
		if err != nil {
			panic(err)
		}

		reader.Seek(0, io.SeekStart)
	}
}

func BenchmarkWriteResponse(b *testing.B) {
	doc := &Document{
		Links: &DocumentLinks{
			Self: "http://0.0.0.0:1234/api/foo/1",
		},
		Data: &HybridResource{
			One: &Resource{
				Type: "foo",
				ID:   "1",
				Attributes: Map{
					"foo": "bar",
					"bar": "baz",
				},
			},
		},
	}

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		err := WriteResponse(httptest.NewRecorder(), http.StatusOK, doc)
		if err != nil {
			panic(err)
		}
	}
}
<|endoftext|>


<commit_before>package drone

import (
	"errors"
	"fmt"
	"io"
)

type CommitService struct {
	*Client
}

// GET /api/repos/{host}/{owner}/{name}/branch/{branch}/commit/{commit}
func (s *CommitService) Get(host, owner, name, branch, sha string) (*Commit, error) {
	var path string
	if s.isServer04 {
		path = fmt.Sprintf("/api/repos/%s/%s/%s", owner, name, sha)
	} else {
		path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s", host, owner, name, branch, sha)
	}
	var commit = Commit{}
	var err = s.run("GET", path, nil, &commit)
	return &commit, err
}

// GET /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}/console
func (s *CommitService) GetOutput(host, owner, name, branch, sha, build_number string) (io.ReadCloser, error) {
	var path string
	if s.isServer04 {
		path = fmt.Sprintf("/api/repos/%s/%s/logs/%s/1", owner, name, build_number)
	} else {
		path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s/console", host, owner, name, branch, sha)
	}
	resp, err := s.do("GET", path)
	if err != nil {
		return nil, nil
	}
	return resp.Body, nil
}

// POST /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}?action=rebuild
// POST /api/repos/{owner}/{name}/builds/{build_number}
func (s *CommitService) Rebuild(host, owner, name, branch, sha, build_number string) error {
	var path string
	if s.isServer04 {
		path = fmt.Sprintf("/api/repos/%s/%s/builds/%s", owner, name, build_number)
	} else {
		path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s?action=rebuild", host, owner, name, branch, sha)
	}
	return s.run("POST", path, nil, nil)
}

// GET /api/repos/{host}/{owner}/{name}/commits
func (s *CommitService) List(host, owner, name string) ([]*Commit, error) {
	var path string
	if s.isServer04 {
		path = fmt.Sprintf("/api/repos/%s/%s/builds", owner, name)
	} else {
		path = fmt.Sprintf("/api/repos/%s/%s/%s/commits", host, owner, name)
	}
	var list []*Commit
	var err = s.run("GET", path, nil, &list)
	return list, err
}

// GET /api/repos/{host}/{owner}/{name}/branch/{branch}
func (s *CommitService) ListBranch(host, owner, name, branch string) ([]*Commit, error) {
	if s.isServer04 {
		return nil, errors.New("No list branch function in Drone 0.4")
	}
	var path = fmt.Sprintf("/api/repos/%s/%s/%s/commits", host, owner, name)
	var list []*Commit
	var err = s.run("GET", path, nil, &list)
	return list, err
}
<commit_msg>Fix a drone endpoint that wasn't returning errors appropriately<commit_after>package drone

import (
	"errors"
	"fmt"
	"io"
)

type CommitService struct {
	*Client
}

// GET /api/repos/{host}/{owner}/{name}/branch/{branch}/commit/{commit}
func (s *CommitService) Get(host, owner, name, branch, sha string) (*Commit, error) {
	var path string
	if s.isServer04 {
		path = fmt.Sprintf("/api/repos/%s/%s/%s", owner, name, sha)
	} else {
		path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s", host, owner, name, branch, sha)
	}
	var commit = Commit{}
	var err = s.run("GET", path, nil, &commit)
	return &commit, err
}

// GET /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}/console
func (s *CommitService) GetOutput(host, owner, name, branch, sha, build_number string) (io.ReadCloser, error) {
	var path string
	if s.isServer04 {
		path = fmt.Sprintf("/api/repos/%s/%s/logs/%s/1", owner, name, build_number)
	} else {
		path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s/console", host, owner, name, branch, sha)
	}
	resp, err := s.do("GET", path)
	if err != nil {
		return nil, err
	}
	return resp.Body, nil
}

// POST /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}?action=rebuild
// POST /api/repos/{owner}/{name}/builds/{build_number}
func (s *CommitService) Rebuild(host, owner, name, branch, sha, build_number string) error {
	var path string
	if s.isServer04 {
		path = fmt.Sprintf("/api/repos/%s/%s/builds/%s", owner, name, build_number)
	} else {
		path = fmt.Sprintf("/api/repos/%s/%s/%s/branches/%s/commits/%s?action=rebuild", host, owner, name, branch, sha)
	}
	return s.run("POST", path, nil, nil)
}

// GET /api/repos/{host}/{owner}/{name}/commits
func (s *CommitService) List(host, owner, name string) ([]*Commit, error) {
	var path string
	if s.isServer04 {
		path = fmt.Sprintf("/api/repos/%s/%s/builds", owner, name)
	} else {
		path = fmt.Sprintf("/api/repos/%s/%s/%s/commits", host, owner, name)
	}
	var list []*Commit
	var err = s.run("GET", path, nil, &list)
	return list, err
}

// GET /api/repos/{host}/{owner}/{name}/branch/{branch}
func (s *CommitService) ListBranch(host, owner, name, branch string) ([]*Commit, error) {
	if s.isServer04 {
		return nil, errors.New("No list branch function in Drone 0.4")
	}
	var path = fmt.Sprintf("/api/repos/%s/%s/%s/commits", host, owner, name)
	var list []*Commit
	var err = s.run("GET", path, nil, &list)
	return list, err
}
<|endoftext|>


<commit_before>package assets

const styles = `
html, body {
    margin: 0;
    padding: 0;
}

body {
    font: 100%/1.3 Verdana, sans-serif;
    overflow-y: scroll;
}

.container {
    max-width: 40rem;
    margin: 1rem;
}

.container.is-wide {
    max-width: 60rem;
}

aside {
    max-width: 40rem;
    margin: 1rem 1.5rem;
}

.container:before, .container:after {
    clear: both;
    content: " ";
    display: table;
}

a {
    color: hsl(220, 51%, 44%);
}

a:hover {
    color: hsl(208, 56%, 38%);
}

a.external-link {
    font-style: italic;
}

header {
    margin: 0 1rem;
    font-size: 1rem;
    border-bottom: 1px solid #ddd;
}

header h1, header > a {
    margin: 0;
    padding: 1.3rem;
    height: 1.3rem;
    line-height: 1.3rem;
    display: inline-block;
}

header h1 {
    font-size: 1.5rem;
    padding-left: 0;
    margin-left: .5rem;
    font-weight: bold;
    align-self: flex-start;
}

header h1 a {
    color: #000;
    text-decoration: none;
}

.repos {
    width: auto;
    list-style: none;
    padding: 0;
    margin: 0;
}

.repos .repo {
    border-bottom: 1px dotted #ddd;
    width: auto;
}

.repos .repo:last-child {
    border-bottom: none;
}

.repo {
    margin: 1.3rem 0;
    padding: 0 .5rem;
    position: relative;
}

.repo h1 {
    font-size: 1.2rem;
    margin: 0;
}

.repo h1 a {
    text-decoration: none;
}

.repo.private a {
    color: hsl(20, 51%, 44%);
}

.repo-header {
    display: flex;
    justify-content: space-between;
    margin: 1rem 0;
    line-height: 1rem;
}

.clone {
    margin: 1.3rem 0;
    background: #efefef;
    padding: 1rem;
}

.filter {
    padding: 1rem 0px;
    background: #fefefe;
    border-bottom: 1px solid #eee;
}

.filter input[type=search] {
    display: block;
    border: none;
    width: 100%;
    padding: 0 0.5rem;
    font: 1rem Verdana, Geneva, sans-serif;
    margin: 0;
    background: none repeat scroll 0% 0% transparent;
}

hr {
    margin: 1.3rem auto;
    border: none;
    height: 0;
    max-width: 10rem;
    width: 66%;
    border-bottom: 1px solid #ccc;
}

figure {
    border: 1px solid #bbb;
    margin: 1rem .5rem;
    padding: 0;
}

figure figcaption {
    border-bottom: 1px solid #ddd;
    padding: 0.65rem 0.65rem;
    margin: 0 0.65rem;
}

figure figcaption h3 {
    margin: 0;
    font-size: .75rem;
}

figure article {
    padding: 0.65rem 1.3rem;
    overflow-x: auto;
}

figure article pre.full {
    padding: 0;
    background: transparent;
}

form {
    margin: 1.3rem 0.5rem;
}

input[type=text] {
    display: block;
    border: none;
    border-bottom: 1px dotted #aaa;
    margin: 0.3rem 0 1.3rem;
    width: 100%;
}

textarea {
    display: block;
    border: 1px dotted #aaa;
    margin: 0.3rem 0 1.3rem;
    width: 100%;
    max-width: 100%;
    min-width: 100%;
}

input[type=submit] {
    margin: 1rem 0;
}

label {
    color: #aaa;
    font-style: italic;
    margin: 0.3rem 0;
}

figure {
    background: white;
    color: black;
}

article {
    font: 13px/1.3 Menlo, monospace;
}

article h1:first-child {
    margin-top: 1rem;
}

article h1 {
    font-size: 16px;
    margin: 2rem 0 1rem;
}

article h2 {
    font-size: 14px;
    margin: 1.5rem 0 0.5rem;
}

article h3 {
    font-size: 13px;
    font-weight: normal;
    text-decoration: underline;
    margin: 1.5rem 0 0.5rem;
}

article ul {
    padding-left: 1.3em;
}

article p {
    margin: 1.3em 0;
}

article code {
    font-family: Menlo, monospace;
    color: rgb(90, 10, 20);
}

article pre > code {
    padding: 0;
    background: none;
    color: rgb(90, 10, 20);
}

article pre {
    padding: .7em 1.3em;
    font-family: Menlo, monospace;
    color: rgb(90, 10, 20);
}

article pre.content {
    color: black;
    padding: 0;
}

article blockquote {
    margin-left: 0;
    padding-left: 1.3em;
    border-left: 1px dotted #aaa;
    color: #666;
}

article hr {
    border: none;
    height: 1px;
    width: 7rem;
    margin: 2.5rem 0 0.5rem;
    background: #aaa;
}

article table {
    border-spacing: collapse;
    border-collapse: collapse;
}

article th, article td {
    padding: 0.3rem 0.5rem;
    margin: 0;
}

article th {
    font-weight: bold;
    text-align: left;
    border-bottom: 1px solid #eee;
}

.files {
    list-style: none;
    padding: 0;
    margin: 0;
}

.files li {
    margin: 0 .65rem;
    padding: .65rem;
    border-bottom: 1px dotted #ddd;
    font-size: .75rem;
}

.files li:last-child {
    border-bottom: none;
}

.files .fa {
    margin-right: .3rem;
    height: 14px;
    width: 14px;
}
`
<commit_msg>Remove box header border if nothing below<commit_after>package assets

const styles = `
html, body {
    margin: 0;
    padding: 0;
}

body {
    font: 100%/1.3 Verdana, sans-serif;
    overflow-y: scroll;
}

.container {
    max-width: 40rem;
    margin: 1rem;
}

.container.is-wide {
    max-width: 60rem;
}

aside {
    max-width: 40rem;
    margin: 1rem 1.5rem;
}

.container:before, .container:after {
    clear: both;
    content: " ";
    display: table;
}

a {
    color: hsl(220, 51%, 44%);
}

a:hover {
    color: hsl(208, 56%, 38%);
}

a.external-link {
    font-style: italic;
}

header {
    margin: 0 1rem;
    font-size: 1rem;
    border-bottom: 1px solid #ddd;
}

header h1, header > a {
    margin: 0;
    padding: 1.3rem;
    height: 1.3rem;
    line-height: 1.3rem;
    display: inline-block;
}

header h1 {
    font-size: 1.5rem;
    padding-left: 0;
    margin-left: .5rem;
    font-weight: bold;
    align-self: flex-start;
}

header h1 a {
    color: #000;
    text-decoration: none;
}

.repos {
    width: auto;
    list-style: none;
    padding: 0;
    margin: 0;
}

.repos .repo {
    border-bottom: 1px dotted #ddd;
    width: auto;
}

.repos .repo:last-child {
    border-bottom: none;
}

.repo {
    margin: 1.3rem 0;
    padding: 0 .5rem;
    position: relative;
}

.repo h1 {
    font-size: 1.2rem;
    margin: 0;
}

.repo h1 a {
    text-decoration: none;
}

.repo.private a {
    color: hsl(20, 51%, 44%);
}

.repo-header {
    display: flex;
    justify-content: space-between;
    margin: 1rem 0;
    line-height: 1rem;
}

.clone {
    margin: 1.3rem 0;
    background: #efefef;
    padding: 1rem;
}

.filter {
    padding: 1rem 0px;
    background: #fefefe;
    border-bottom: 1px solid #eee;
}

.filter input[type=search] {
    display: block;
    border: none;
    width: 100%;
    padding: 0 0.5rem;
    font: 1rem Verdana, Geneva, sans-serif;
    margin: 0;
    background: none repeat scroll 0% 0% transparent;
}

hr {
    margin: 1.3rem auto;
    border: none;
    height: 0;
    max-width: 10rem;
    width: 66%;
    border-bottom: 1px solid #ccc;
}

figure {
    border: 1px solid #bbb;
    margin: 1rem .5rem;
    padding: 0;
}

figure figcaption {
    border-bottom: 1px solid #ddd;
    padding: 0.65rem 0.65rem;
    margin: 0 0.65rem;
}

figure figcaption:last-child {
    border-bottom: none;
}

figure figcaption h3 {
    margin: 0;
    font-size: .75rem;
}

figure article {
    padding: 0.65rem 1.3rem;
    overflow-x: auto;
}

figure article pre.full {
    padding: 0;
    background: transparent;
}

form {
    margin: 1.3rem 0.5rem;
}

input[type=text] {
    display: block;
    border: none;
    border-bottom: 1px dotted #aaa;
    margin: 0.3rem 0 1.3rem;
    width: 100%;
}

textarea {
    display: block;
    border: 1px dotted #aaa;
    margin: 0.3rem 0 1.3rem;
    width: 100%;
    max-width: 100%;
    min-width: 100%;
}

input[type=submit] {
    margin: 1rem 0;
}

label {
    color: #aaa;
    font-style: italic;
    margin: 0.3rem 0;
}

figure {
    background: white;
    color: black;
}

article {
    font: 13px/1.3 Menlo, monospace;
}

article h1:first-child {
    margin-top: 1rem;
}

article h1 {
    font-size: 16px;
    margin: 2rem 0 1rem;
}

article h2 {
    font-size: 14px;
    margin: 1.5rem 0 0.5rem;
}

article h3 {
    font-size: 13px;
    font-weight: normal;
    text-decoration: underline;
    margin: 1.5rem 0 0.5rem;
}

article ul {
    padding-left: 1.3em;
}

article p {
    margin: 1.3em 0;
}

article code {
    font-family: Menlo, monospace;
    color: rgb(90, 10, 20);
}

article pre > code {
    padding: 0;
    background: none;
    color: rgb(90, 10, 20);
}

article pre {
    padding: .7em 1.3em;
    font-family: Menlo, monospace;
    color: rgb(90, 10, 20);
}

article pre.content {
    color: black;
    padding: 0;
}

article blockquote {
    margin-left: 0;
    padding-left: 1.3em;
    border-left: 1px dotted #aaa;
    color: #666;
}

article hr {
    border: none;
    height: 1px;
    width: 7rem;
    margin: 2.5rem 0 0.5rem;
    background: #aaa;
}

article table {
    border-spacing: collapse;
    border-collapse: collapse;
}

article th, article td {
    padding: 0.3rem 0.5rem;
    margin: 0;
}

article th {
    font-weight: bold;
    text-align: left;
    border-bottom: 1px solid #eee;
}

.files {
    list-style: none;
    padding: 0;
    margin: 0;
}

.files li {
    margin: 0 .65rem;
    padding: .65rem;
    border-bottom: 1px dotted #ddd;
    font-size: .75rem;
}

.files li:last-child {
    border-bottom: none;
}

.files .fa {
    margin-right: .3rem;
    height: 14px;
    width: 14px;
}
`
<|endoftext|>