<commit_before>package learn

import (
	"github.com/tsinghua-io/api-server/model"
	"github.com/tsinghua-io/api-server/util"
	"net/http"
	"testing"
)

func TestAssignments(t *testing.T) {
	actual, status, err := ada.Assignments("127743")
	if err != nil {
		t.Fatalf("Failed to get assignments: %s", err)
	}

	util.ExpectStatus(t, status, http.StatusOK)

	// Check fetched data.
	expected := []*model.Assignment{
		{
			Id:       "663857",
			CourseId: "127743",
			BeginAt:  "2015-11-02",
			DueAt:    "2015-11-05",
			Title:    "第六次作业",
			Body:     "    第六次作业有部分内容需要Matlab仿真，所以如果有同学想提交电子版作业，请把非仿真的部分也拍照一并提交上来，这样就可以不必提交纸版作业了。\n    如果有同学要提交纸板作业，请把仿真内容打印出来，在课堂上交给老师！\n",
			Submissions: []*model.Submission{
				{
					AssignmentId: "663857",
					Late:         false,
					Body:         "",
					Attachment: &model.Attachment{
						Filename:    "2013011187_663857_873609575_p4.pdf",
						Size:        2820876,
						DownloadURL: "https://learn.tsinghua.edu.cn/uploadFile/downloadFile.jsp?module_id=322\u0026course_id=127743\u0026filePath=Ui6dWfN3E23iy92Lm3GqLolVIj%2Bu5tfsytg7jRwlhOqxaULAEWF80pMjNsAbeGoNLbxf932lsPZSeaPFGySxlzqYaxPQuvWF9JTL%2B1WuOg4%3D",
					},
					MarkedAt: "2015-11-06",
					Mark:     util.NewFloat32(9.5),
					Comment:  "第二题分析和结论正确，公式有问题。",
				},
			},
		},
		{
			Id:       "667021",
			CourseId: "127743",
			BeginAt:  "2015-11-15",
			DueAt:    "2015-11-30",
			Title:    "大作业",
			Body:     "作业说明件附件，参考论文和数据请从“课程文件”中下载。\n\n",
			Attachment: &model.Attachment{
				Filename:    "625602385_2_2015年《数字信号处理》课程大作业.pdf",
				Size:        112055,
				DownloadURL: "https://learn.tsinghua.edu.cn/uploadFile/downloadFile.jsp?module_id=322\u0026course_id=127743\u0026filePath=7D5eM/3uxuWgUscnZFe5xYFRwCtzmT3Nd4b8XfYdVt9QXP6jW0X3Mw6gr2ogb0t8bD67/q7AeDDvr3x32279mpdW6Tj5nS6ysO1fFyPcUzk%3D",
			},
			Submissions: []*model.Submission{
				{
					AssignmentId: "667021",
					Late:         false,
					Body:         "",
					Attachment: &model.Attachment{
						Filename:    "2013011187_667021_531504538_report.pdf",
						Size:        98640,
						DownloadURL: "https://learn.tsinghua.edu.cn/uploadFile/downloadFile.jsp?module_id=322\u0026course_id=127743\u0026filePath=Ui6dWfN3E23iy92Lm3GqLolVIj%2Bu5tfsVetxSI%2BmeI5zL/GWM0GkxzPppRm00efUNVY7MLZOt3A1jm56tM3YdeAlZMTa30DiABpxaPmB1YI%3D",
					},
				},
			},
		},
		{
			Id:       "669225",
			CourseId: "127743",
			BeginAt:  "2015-11-24",
			DueAt:    "2015-11-26",
			Title:    "第九次作业",
			Body:     "",
		},
		{
			Id:       "670485",
			CourseId: "127743",
			BeginAt:  "2015-11-30",
			DueAt:    "2015-12-04",
			Title:    "第十次作业",
			Body:     "请未交作业的同学尽快提交作业！",
		},
		{
			Id:       "672559",
			CourseId: "127743",
			BeginAt:  "2015-12-09",
			DueAt:    "2015-12-11",
			Title:    "第十一次作业",
			Body:     "",
		},
		{
			Id:       "674369",
			CourseId: "127743",
			BeginAt:  "2015-12-15",
			DueAt:    "2015-12-18",
			Title:    "第十二次作业",
			Body:     "",
		},
		{
			Id:       "675999",
			CourseId: "127743",
			BeginAt:  "2015-12-22",
			DueAt:    "2015-12-25",
			Title:    "第十三次作业",
			Body:     "",
		},
		{
			Id:       "677925",
			CourseId: "127743",
			BeginAt:  "2015-12-30",
			DueAt:    "2016-01-01",
			Title:    "第十四次作业",
			Body:     "",
			Submissions: []*model.Submission{
				{
					AssignmentId: "677925",
					Late:         false,
					Body:         "助教你好，这是最后的三次作业（对于课件12，13，14，15），一起交上来了！",
					Attachment: &model.Attachment{
						Filename:    "2013011187_677925_351002502_课件12-15对应的作业.zip",
						Size:        13601942,
						DownloadURL: "https://learn.tsinghua.edu.cn/uploadFile/downloadFile.jsp?module_id=322\u0026course_id=127743\u0026filePath=Ui6dWfN3E23iy92Lm3GqLolVIj%2Bu5tfsAqZGEzdQ71%2BH77AyehAScSLl2e2n5PeFBbCCeknxk/vScQfKcoZU4o%2B/fgVxuHWOFnv5a2X8eUU%3D",
					},
					MarkedAt: "2016-01-03",
					Mark:     util.NewFloat32(9.5),
					Comment:  "前面三次各8分",
				},
			},
		},
	}

	util.ExpectDeepEqual(t, actual, expected)
}

func BenchmarkAssignments(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ada.Assignments("127743")
	}
}
<commit_msg>Update assignment tests.<commit_after>package learn

import (
	"github.com/tsinghua-io/api-server/model"
	"github.com/tsinghua-io/api-server/util"
	"net/http"
	"testing"
)

func TestAssignments(t *testing.T) {
	actual, status, err := ada.Assignments("127743")
	if err != nil {
		t.Fatalf("Failed to get assignments: %s", err)
	}

	util.ExpectStatus(t, status, http.StatusOK)

	// Check fetched data.
	expected := []*model.Assignment{
		{
			Id:       "663857",
			CourseId: "127743",
			BeginAt:  "2015-11-02",
			DueAt:    "2015-11-05",
			Title:    "第六次作业",
			Body:     "第六次作业有部分内容需要Matlab仿真，所以如果有同学想提交电子版作业，请把非仿真的部分也拍照一并提交上来，这样就可以不必提交纸版作业了。\n    如果有同学要提交纸板作业，请把仿真内容打印出来，在课堂上交给老师！",
			Submissions: []*model.Submission{
				{
					AssignmentId: "663857",
					Late:         false,
					Body:         "",
					Attachment: &model.Attachment{
						Filename:    "2013011187_663857_873609575_p4.pdf",
						Size:        2820876,
						DownloadURL: "https://learn.tsinghua.edu.cn/uploadFile/downloadFile.jsp?module_id=322\u0026course_id=127743\u0026filePath=Ui6dWfN3E23iy92Lm3GqLolVIj%2Bu5tfsytg7jRwlhOqxaULAEWF80pMjNsAbeGoNLbxf932lsPZSeaPFGySxlzqYaxPQuvWF9JTL%2B1WuOg4%3D",
					},
					MarkedAt: "2015-11-06",
					Mark:     util.NewFloat32(9.5),
					Comment:  "第二题分析和结论正确，公式有问题。",
				},
			},
		},
		{
			Id:       "667021",
			CourseId: "127743",
			BeginAt:  "2015-11-15",
			DueAt:    "2015-11-30",
			Title:    "大作业",
			Body:     "作业说明件附件，参考论文和数据请从“课程文件”中下载。",
			Attachment: &model.Attachment{
				Filename:    "625602385_2_2015年《数字信号处理》课程大作业.pdf",
				Size:        112055,
				DownloadURL: "https://learn.tsinghua.edu.cn/uploadFile/downloadFile.jsp?module_id=322\u0026course_id=127743\u0026filePath=7D5eM/3uxuWgUscnZFe5xYFRwCtzmT3Nd4b8XfYdVt9QXP6jW0X3Mw6gr2ogb0t8bD67/q7AeDDvr3x32279mpdW6Tj5nS6ysO1fFyPcUzk%3D",
			},
			Submissions: []*model.Submission{
				{
					AssignmentId: "667021",
					Late:         false,
					Body:         "",
					Attachment: &model.Attachment{
						Filename:    "2013011187_667021_531504538_report.pdf",
						Size:        98640,
						DownloadURL: "https://learn.tsinghua.edu.cn/uploadFile/downloadFile.jsp?module_id=322\u0026course_id=127743\u0026filePath=Ui6dWfN3E23iy92Lm3GqLolVIj%2Bu5tfsVetxSI%2BmeI5zL/GWM0GkxzPppRm00efUNVY7MLZOt3A1jm56tM3YdeAlZMTa30DiABpxaPmB1YI%3D",
					},
				},
			},
		},
		{
			Id:       "669225",
			CourseId: "127743",
			BeginAt:  "2015-11-24",
			DueAt:    "2015-11-26",
			Title:    "第九次作业",
			Body:     "",
		},
		{
			Id:       "670485",
			CourseId: "127743",
			BeginAt:  "2015-11-30",
			DueAt:    "2015-12-04",
			Title:    "第十次作业",
			Body:     "请未交作业的同学尽快提交作业！",
		},
		{
			Id:       "672559",
			CourseId: "127743",
			BeginAt:  "2015-12-09",
			DueAt:    "2015-12-11",
			Title:    "第十一次作业",
			Body:     "",
		},
		{
			Id:       "674369",
			CourseId: "127743",
			BeginAt:  "2015-12-15",
			DueAt:    "2015-12-18",
			Title:    "第十二次作业",
			Body:     "",
		},
		{
			Id:       "675999",
			CourseId: "127743",
			BeginAt:  "2015-12-22",
			DueAt:    "2015-12-25",
			Title:    "第十三次作业",
			Body:     "",
		},
		{
			Id:       "677925",
			CourseId: "127743",
			BeginAt:  "2015-12-30",
			DueAt:    "2016-01-01",
			Title:    "第十四次作业",
			Body:     "",
			Submissions: []*model.Submission{
				{
					AssignmentId: "677925",
					Late:         false,
					Body:         "助教你好，这是最后的三次作业（对于课件12，13，14，15），一起交上来了！",
					Attachment: &model.Attachment{
						Filename:    "2013011187_677925_351002502_课件12-15对应的作业.zip",
						Size:        13601942,
						DownloadURL: "https://learn.tsinghua.edu.cn/uploadFile/downloadFile.jsp?module_id=322\u0026course_id=127743\u0026filePath=Ui6dWfN3E23iy92Lm3GqLolVIj%2Bu5tfsAqZGEzdQ71%2BH77AyehAScSLl2e2n5PeFBbCCeknxk/vScQfKcoZU4o%2B/fgVxuHWOFnv5a2X8eUU%3D",
					},
					MarkedAt: "2016-01-03",
					Mark:     util.NewFloat32(9.5),
					Comment:  "前面三次各8分",
				},
			},
		},
	}

	util.ExpectDeepEqual(t, actual, expected)
}

func BenchmarkAssignments(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ada.Assignments("127743")
	}
}
<|endoftext|>


<commit_before>package integration_test

import (
	"net/http"
	"os/exec"

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

	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"github.com/onsi/gomega/ghttp"

	"github.com/concourse/atc"
)

var _ = Describe("AbortBuild", func() {
	BeforeEach(func() {
		expectedBuild := atc.Build{
			ID:      23,
			Name:    "42",
			Status:  "running",
			JobName: "myjob",
			URL:     "/pipelines/my-pipeline/jobs/my-job/builds/42",
			APIURL:  "api/v1/builds/123",
		}

		expectedJobBuildURL := "/api/v1/teams/main/pipelines/my-pipeline/jobs/my-job/builds/42"
		expectedAbortURL := "/api/v1/builds/23/abort"

		atcServer.AppendHandlers(
			ghttp.CombineHandlers(
				ghttp.VerifyRequest("GET", expectedJobBuildURL),
				ghttp.RespondWithJSONEncoded(http.StatusOK, expectedBuild),
			),
			ghttp.CombineHandlers(
				ghttp.VerifyRequest("PUT", expectedAbortURL),
				ghttp.RespondWith(http.StatusNoContent, ""),
			),
		)
	})

	Context("when the job name is not specified", func() {
		BeforeEach(func() {
			expectedBuild := atc.Build{
				ID:      23,
				Name:    "42",
				Status:  "running",
				JobName: "myjob",
				URL:     "/pipelines/my-pipeline/jobs/my-job/builds/42",
				APIURL:  "api/v1/builds/123",
			}

			expectedJobBuildURL := "/api/v1/builds/23"

			atcServer.SetHandler(4, ghttp.CombineHandlers(
				ghttp.VerifyRequest("GET", expectedJobBuildURL),
				ghttp.RespondWithJSONEncoded(http.StatusOK, expectedBuild),
			))
		})

		It("aborts the build", func() {
			Expect(func() {
				flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-b", "23")

				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(sess).Should(gexec.Exit(0))

				Expect(sess.Out).To(gbytes.Say("build successfully aborted"))
			}).To(Change(func() int {
				return len(atcServer.ReceivedRequests())
			}).By(3))
		})
	})

	Context("when the build name is not specified", func() {
		It("asks the user to specifiy a build name", func() {
			flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-j", "some-pipeline-name/some-job-name")

			sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())

			Eventually(sess).Should(gexec.Exit(1))

			Expect(sess.Err).To(gbytes.Say("error: the required flag `" + osFlag("b", "build") + "' was not specified"))
		})
	})

	Context("when the pipeline/build exists", func() {
		It("aborts the build", func() {
			Expect(func() {
				flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-j", "my-pipeline/my-job", "-b", "42")

				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(sess).Should(gexec.Exit(0))

				Expect(sess.Out).To(gbytes.Say("build successfully aborted"))
			}).To(Change(func() int {
				return len(atcServer.ReceivedRequests())
			}).By(3))
		})
	})

	Context("when the build or pipeline does not exist", func() {
		BeforeEach(func() {
			expectedJobBuildURL := "/api/v1/teams/main/pipelines/my-pipeline/jobs/my-job/builds/42"

			atcServer.SetHandler(4, ghttp.CombineHandlers(
				ghttp.VerifyRequest("GET", expectedJobBuildURL),
				ghttp.RespondWith(http.StatusNotFound, "{}"),
			))
		})

		It("returns a helpful error message", func() {
			Expect(func() {
				flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-j", "my-pipeline/my-job", "-b", "42")
				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(sess).Should(gexec.Exit(1))
				Expect(sess.Err).To(gbytes.Say("error: build does not exist"))
			}).To(Change(func() int {
				return len(atcServer.ReceivedRequests())
			}).By(2))
		})
	})
})
<commit_msg>Refactor abort build integration tests<commit_after>package integration_test

import (
	"net/http"
	"os/exec"

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

	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"github.com/onsi/gomega/ghttp"

	"github.com/concourse/atc"
)

var _ = Describe("AbortBuild", func() {
	var expectedAbortURL = "/api/v1/builds/23/abort"

	var expectedBuild = atc.Build{
		ID:      23,
		Name:    "42",
		Status:  "running",
		JobName: "myjob",
		URL:     "/pipelines/my-pipeline/jobs/my-job/builds/42",
		APIURL:  "api/v1/builds/123",
	}

	Context("when the job name is not specified", func() {
		Context("and the build id is specified", func() {
			BeforeEach(func() {
				expectedURL := "/api/v1/builds/23"

				atcServer.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", expectedURL),
						ghttp.RespondWithJSONEncoded(http.StatusOK, expectedBuild),
					),

					ghttp.CombineHandlers(
						ghttp.VerifyRequest("PUT", expectedAbortURL),
						ghttp.RespondWith(http.StatusNoContent, ""),
					),
				)
			})

			It("aborts the build", func() {
				Expect(func() {
					flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-b", "23")

					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
					Expect(err).NotTo(HaveOccurred())

					Eventually(sess).Should(gexec.Exit(0))

					Expect(sess.Out).To(gbytes.Say("build successfully aborted"))
				}).To(Change(func() int {
					return len(atcServer.ReceivedRequests())
				}).By(3))
			})
		})

		Context("and the build id does not exist", func() {
			BeforeEach(func() {
				expectedURL := "/api/v1/builds/42"

				atcServer.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", expectedURL),
						ghttp.RespondWith(http.StatusNotFound, ""),
					),
				)
			})

			It("asks the user to specifiy a build id", func() {
				flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-b", "42")

				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(sess).Should(gexec.Exit(1))

				Expect(sess.Err).To(gbytes.Say("error: build does not exist"))
			})
		})

		Context("and the build id is not specified", func() {
			It("asks the user to specifiy a build id", func() {
				flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build")

				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(sess).Should(gexec.Exit(1))

				Expect(sess.Err).To(gbytes.Say("error: the required flag `" + osFlag("b", "build") + "' was not specified"))
			})
		})
	})

	Context("when the pipeline/build exists", func() {
		Context("and the build name is specified", func() {
			BeforeEach(func() {
				expectedURL := "/api/v1/teams/main/pipelines/my-pipeline/jobs/my-job/builds/42"

				atcServer.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", expectedURL),
						ghttp.RespondWithJSONEncoded(http.StatusOK, expectedBuild),
					),

					ghttp.CombineHandlers(
						ghttp.VerifyRequest("PUT", expectedAbortURL),
						ghttp.RespondWith(http.StatusNoContent, ""),
					),
				)
			})

			It("aborts the build", func() {
				Expect(func() {
					flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-j", "my-pipeline/my-job", "-b", "42")

					sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
					Expect(err).NotTo(HaveOccurred())

					Eventually(sess).Should(gexec.Exit(0))

					Expect(sess.Out).To(gbytes.Say("build successfully aborted"))
				}).To(Change(func() int {
					return len(atcServer.ReceivedRequests())
				}).By(3))
			})
		})

		Context("and the build number does not exist", func() {
			BeforeEach(func() {
				expectedURL := "/api/v1/teams/main/pipelines/my-pipeline/jobs/my-job/builds/23"

				atcServer.AppendHandlers(
					ghttp.CombineHandlers(
						ghttp.VerifyRequest("GET", expectedURL),
						ghttp.RespondWith(http.StatusNotFound, ""),
					),
				)
			})

			It("asks the user to specifiy a build name", func() {
				flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-j", "my-pipeline/my-job", "-b", "23")

				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(sess).Should(gexec.Exit(1))

				Expect(sess.Err).To(gbytes.Say("error: build does not exist"))
			})
		})

		Context("and the build name is not specified", func() {
			It("asks the user to specifiy a build name", func() {
				flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-j", "some-pipeline-name/some-job-name")

				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(sess).Should(gexec.Exit(1))

				Expect(sess.Err).To(gbytes.Say("error: the required flag `" + osFlag("b", "build") + "' was not specified"))
			})
		})
	})

	Context("when the build or pipeline does not exist", func() {
		BeforeEach(func() {
			expectedJobBuildURL := "/api/v1/teams/main/pipelines/my-pipeline/jobs/my-job/builds/42"

			atcServer.AppendHandlers(
				ghttp.CombineHandlers(
					ghttp.VerifyRequest("GET", expectedJobBuildURL),
					ghttp.RespondWith(http.StatusNotFound, "{}"),
				),
			)
		})

		It("returns a helpful error message", func() {
			Expect(func() {
				flyCmd := exec.Command(flyPath, "-t", targetName, "abort-build", "-j", "my-pipeline/my-job", "-b", "42")
				sess, err := gexec.Start(flyCmd, GinkgoWriter, GinkgoWriter)
				Expect(err).NotTo(HaveOccurred())

				Eventually(sess).Should(gexec.Exit(1))
				Expect(sess.Err).To(gbytes.Say("error: build does not exist"))
			}).To(Change(func() int {
				return len(atcServer.ReceivedRequests())
			}).By(2))
		})
	})
})
<|endoftext|>


<commit_before>package integrationtests

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"

	"github.com/tebeka/selenium"

	"github.com/lucas-clemente/quic-go/protocol"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const nImgs = 200
const imgSize = 40

func init() {
	http.HandleFunc("/tile", func(w http.ResponseWriter, r *http.Request) {
		// Small 40x40 png
		w.Write([]byte{
			0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
			0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28,
			0x01, 0x03, 0x00, 0x00, 0x00, 0xb6, 0x30, 0x2a, 0x2e, 0x00, 0x00, 0x00,
			0x03, 0x50, 0x4c, 0x54, 0x45, 0x5a, 0xc3, 0x5a, 0xad, 0x38, 0xaa, 0xdb,
			0x00, 0x00, 0x00, 0x0b, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0x63, 0x18,
			0x61, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x01, 0xe2, 0xb8, 0x75, 0x22, 0x00,
			0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
		})
	})

	http.HandleFunc("/tiles", func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, "")
		for i := 0; i < nImgs; i++ {
			fmt.Fprintf(w, `<img src="/tile?cachebust=%d">`, i)
		}
		io.WriteString(w, "")
	})
}

var _ = Describe("Chrome tests", func() {
	It("does not work with mismatching versions", func() {
		versionForUs := protocol.SupportedVersions[0]
		versionForChrome := protocol.SupportedVersions[len(protocol.SupportedVersions)-1]

		// If both are equal, this test doesn't make any sense.
		if versionForChrome == versionForUs {
			return
		}

		supportedVersionsBefore := protocol.SupportedVersions
		protocol.SupportedVersions = []protocol.VersionNumber{versionForUs}
		wd := getWebdriverForVersion(versionForChrome)

		defer func() {
			protocol.SupportedVersions = supportedVersionsBefore
			wd.Close()
		}()

		err := wd.Get("https://quic.clemente.io/hello")
		Expect(err).NotTo(HaveOccurred())
		source, err := wd.PageSource()
		Expect(err).NotTo(HaveOccurred())
		Expect(source).ToNot(ContainSubstring("Hello, World!\n"))
	})

	for i := range protocol.SupportedVersions {
		version := protocol.SupportedVersions[i]

		Context(fmt.Sprintf("with quic version %d", version), func() {
			var (
				wd                      selenium.WebDriver
				supportedVersionsBefore []protocol.VersionNumber
			)

			BeforeEach(func() {
				supportedVersionsBefore = protocol.SupportedVersions
				protocol.SupportedVersions = []protocol.VersionNumber{version}
				wd = getWebdriverForVersion(version)
			})

			AfterEach(func() {
				wd.Close()
				protocol.SupportedVersions = supportedVersionsBefore
			})

			It("loads a simple hello world page using quic", func(done Done) {
				err := wd.Get("https://quic.clemente.io/hello")
				Expect(err).NotTo(HaveOccurred())
				source, err := wd.PageSource()
				Expect(err).NotTo(HaveOccurred())
				Expect(source).To(ContainSubstring("Hello, World!\n"))
				close(done)
			}, 5)

			It("loads a large number of files", func(done Done) {
				err := wd.Get("https://quic.clemente.io/tiles")
				Expect(err).NotTo(HaveOccurred())
				Eventually(func() error {
					imgs, err := wd.FindElements("tag name", "img")
					if err != nil {
						return err
					}
					if len(imgs) != nImgs {
						return fmt.Errorf("expected number of images to be %d, got %d", nImgs, len(imgs))
					}
					for i, img := range imgs {
						size, err := img.Size()
						if err != nil {
							return err
						}
						if size.Height != imgSize || size.Width != imgSize {
							return fmt.Errorf("image %d did not have expected size", i)
						}
					}
					return nil
				}, 5).ShouldNot(HaveOccurred())
				close(done)
			}, 10)

			It("downloads a small file", func() {
				dataMan.GenerateData(dataLen)
				err := wd.Get("https://quic.clemente.io/data")
				Expect(err).NotTo(HaveOccurred())
				Eventually(func() int { return getDownloadSize("data") }, 30, 0.1).Should(Equal(dataLen))
				Expect(getDownloadMD5("data")).To(Equal(dataMan.GetMD5()))
			}, 60)

			It("downloads a large file", func() {
				dataMan.GenerateData(dataLongLen)
				err := wd.Get("https://quic.clemente.io/data")
				Expect(err).NotTo(HaveOccurred())
				Eventually(func() int { return getDownloadSize("data") }, 90, 0.5).Should(Equal(dataLongLen))
				Expect(getDownloadMD5("data")).To(Equal(dataMan.GetMD5()))
			}, 100)

			It("uploads a small file", func() {
				dataMan.GenerateData(dataLen)
				data := dataMan.GetData()
				dir, err := ioutil.TempDir("", "quic-upload-src")
				Expect(err).ToNot(HaveOccurred())
				defer os.RemoveAll(dir)
				tmpfn := filepath.Join(dir, "data.dat")
				err = ioutil.WriteFile(tmpfn, data, 0777)
				Expect(err).ToNot(HaveOccurred())
				copyFileToDocker(tmpfn)

				err = wd.Get("https://quic.clemente.io/uploadform?num=1")
				Expect(err).NotTo(HaveOccurred())
				elem, err := wd.FindElement(selenium.ByCSSSelector, "#upload_0")
				Expect(err).ToNot(HaveOccurred())
				err = elem.SendKeys("/home/seluser/data.dat")
				Expect(err).ToNot(HaveOccurred())
				Eventually(func() error { return elem.Submit() }, 30, 0.1).ShouldNot(HaveOccurred())

				file := filepath.Join(uploadDir, "data.dat")
				Expect(getFileSize(file)).To(Equal(dataLen))
				Expect(getFileMD5(file)).To(Equal(dataMan.GetMD5()))
			})

			It("uploads a large file", func() {
				dataMan.GenerateData(dataLongLen)
				data := dataMan.GetData()
				dir, err := ioutil.TempDir("", "quic-upload-src")
				Expect(err).ToNot(HaveOccurred())
				defer os.RemoveAll(dir)
				tmpfn := filepath.Join(dir, "data.dat")
				err = ioutil.WriteFile(tmpfn, data, 0777)
				Expect(err).ToNot(HaveOccurred())
				copyFileToDocker(tmpfn)

				err = wd.Get("https://quic.clemente.io/uploadform?num=1")
				Expect(err).NotTo(HaveOccurred())
				elem, err := wd.FindElement(selenium.ByCSSSelector, "#upload_0")
				Expect(err).ToNot(HaveOccurred())
				err = elem.SendKeys("/home/seluser/data.dat")
				Expect(err).ToNot(HaveOccurred())
				Eventually(func() error { return elem.Submit() }, 90, 0.5).ShouldNot(HaveOccurred())

				file := filepath.Join(uploadDir, "data.dat")
				Expect(getFileSize(file)).To(Equal(dataLongLen))
				Expect(getFileMD5(file)).To(Equal(dataMan.GetMD5()))
			})
		})
	}
})
<commit_msg>add an integration test for uploading many files at once<commit_after>package integrationtests

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strconv"

	"github.com/tebeka/selenium"

	"github.com/lucas-clemente/quic-go/protocol"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

const nImgs = 200
const imgSize = 40

func init() {
	http.HandleFunc("/tile", func(w http.ResponseWriter, r *http.Request) {
		// Small 40x40 png
		w.Write([]byte{
			0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
			0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x28,
			0x01, 0x03, 0x00, 0x00, 0x00, 0xb6, 0x30, 0x2a, 0x2e, 0x00, 0x00, 0x00,
			0x03, 0x50, 0x4c, 0x54, 0x45, 0x5a, 0xc3, 0x5a, 0xad, 0x38, 0xaa, 0xdb,
			0x00, 0x00, 0x00, 0x0b, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0x63, 0x18,
			0x61, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x01, 0xe2, 0xb8, 0x75, 0x22, 0x00,
			0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
		})
	})

	http.HandleFunc("/tiles", func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, "")
		for i := 0; i < nImgs; i++ {
			fmt.Fprintf(w, `<img src="/tile?cachebust=%d">`, i)
		}
		io.WriteString(w, "")
	})
}

var _ = Describe("Chrome tests", func() {
	It("does not work with mismatching versions", func() {
		versionForUs := protocol.SupportedVersions[0]
		versionForChrome := protocol.SupportedVersions[len(protocol.SupportedVersions)-1]

		// If both are equal, this test doesn't make any sense.
		if versionForChrome == versionForUs {
			return
		}

		supportedVersionsBefore := protocol.SupportedVersions
		protocol.SupportedVersions = []protocol.VersionNumber{versionForUs}
		wd := getWebdriverForVersion(versionForChrome)

		defer func() {
			protocol.SupportedVersions = supportedVersionsBefore
			wd.Close()
		}()

		err := wd.Get("https://quic.clemente.io/hello")
		Expect(err).NotTo(HaveOccurred())
		source, err := wd.PageSource()
		Expect(err).NotTo(HaveOccurred())
		Expect(source).ToNot(ContainSubstring("Hello, World!\n"))
	})

	for i := range protocol.SupportedVersions {
		version := protocol.SupportedVersions[i]

		Context(fmt.Sprintf("with quic version %d", version), func() {
			var (
				wd                      selenium.WebDriver
				supportedVersionsBefore []protocol.VersionNumber
			)

			BeforeEach(func() {
				supportedVersionsBefore = protocol.SupportedVersions
				protocol.SupportedVersions = []protocol.VersionNumber{version}
				wd = getWebdriverForVersion(version)
			})

			AfterEach(func() {
				wd.Close()
				protocol.SupportedVersions = supportedVersionsBefore
			})

			It("loads a simple hello world page using quic", func(done Done) {
				err := wd.Get("https://quic.clemente.io/hello")
				Expect(err).NotTo(HaveOccurred())
				source, err := wd.PageSource()
				Expect(err).NotTo(HaveOccurred())
				Expect(source).To(ContainSubstring("Hello, World!\n"))
				close(done)
			}, 5)

			It("loads a large number of files", func(done Done) {
				err := wd.Get("https://quic.clemente.io/tiles")
				Expect(err).NotTo(HaveOccurred())
				Eventually(func() error {
					imgs, err := wd.FindElements("tag name", "img")
					if err != nil {
						return err
					}
					if len(imgs) != nImgs {
						return fmt.Errorf("expected number of images to be %d, got %d", nImgs, len(imgs))
					}
					for i, img := range imgs {
						size, err := img.Size()
						if err != nil {
							return err
						}
						if size.Height != imgSize || size.Width != imgSize {
							return fmt.Errorf("image %d did not have expected size", i)
						}
					}
					return nil
				}, 5).ShouldNot(HaveOccurred())
				close(done)
			}, 10)

			It("downloads a small file", func() {
				dataMan.GenerateData(dataLen)
				err := wd.Get("https://quic.clemente.io/data")
				Expect(err).NotTo(HaveOccurred())
				Eventually(func() int { return getDownloadSize("data") }, 30, 0.1).Should(Equal(dataLen))
				Expect(getDownloadMD5("data")).To(Equal(dataMan.GetMD5()))
			}, 60)

			It("downloads a large file", func() {
				dataMan.GenerateData(dataLongLen)
				err := wd.Get("https://quic.clemente.io/data")
				Expect(err).NotTo(HaveOccurred())
				Eventually(func() int { return getDownloadSize("data") }, 90, 0.5).Should(Equal(dataLongLen))
				Expect(getDownloadMD5("data")).To(Equal(dataMan.GetMD5()))
			}, 100)

			It("uploads a small file", func() {
				dataMan.GenerateData(dataLen)
				data := dataMan.GetData()
				dir, err := ioutil.TempDir("", "quic-upload-src")
				Expect(err).ToNot(HaveOccurred())
				defer os.RemoveAll(dir)
				tmpfn := filepath.Join(dir, "data.dat")
				err = ioutil.WriteFile(tmpfn, data, 0777)
				Expect(err).ToNot(HaveOccurred())
				copyFileToDocker(tmpfn)

				err = wd.Get("https://quic.clemente.io/uploadform?num=1")
				Expect(err).NotTo(HaveOccurred())
				elem, err := wd.FindElement(selenium.ByCSSSelector, "#upload_0")
				Expect(err).ToNot(HaveOccurred())
				err = elem.SendKeys("/home/seluser/data.dat")
				Expect(err).ToNot(HaveOccurred())
				Eventually(func() error { return elem.Submit() }, 30, 0.1).ShouldNot(HaveOccurred())

				file := filepath.Join(uploadDir, "data.dat")
				Expect(getFileSize(file)).To(Equal(dataLen))
				Expect(getFileMD5(file)).To(Equal(dataMan.GetMD5()))
			})

			It("uploads a large file", func() {
				dataMan.GenerateData(dataLongLen)
				data := dataMan.GetData()
				dir, err := ioutil.TempDir("", "quic-upload-src")
				Expect(err).ToNot(HaveOccurred())
				defer os.RemoveAll(dir)
				tmpfn := filepath.Join(dir, "data.dat")
				err = ioutil.WriteFile(tmpfn, data, 0777)
				Expect(err).ToNot(HaveOccurred())
				copyFileToDocker(tmpfn)

				err = wd.Get("https://quic.clemente.io/uploadform?num=1")
				Expect(err).NotTo(HaveOccurred())
				elem, err := wd.FindElement(selenium.ByCSSSelector, "#upload_0")
				Expect(err).ToNot(HaveOccurred())
				err = elem.SendKeys("/home/seluser/data.dat")
				Expect(err).ToNot(HaveOccurred())
				Eventually(func() error { return elem.Submit() }, 90, 0.5).ShouldNot(HaveOccurred())

				file := filepath.Join(uploadDir, "data.dat")
				Expect(getFileSize(file)).To(Equal(dataLongLen))
				Expect(getFileMD5(file)).To(Equal(dataMan.GetMD5()))
			})

			// this test takes a long time because it copies every file into the docker container one by one
			// unfortunately, docker doesn't support copying multiple files at once
			// see https://github.com/docker/docker/issues/7710
			It("uploads many small files", func() {
				num := protocol.MaxStreamsPerConnection + 20

				dir, err := ioutil.TempDir("", "quic-upload-src")
				Expect(err).ToNot(HaveOccurred())
				defer os.RemoveAll(dir)

				var md5s [][]byte

				for i := 0; i < num; i++ {
					dataMan.GenerateData(dataLen)
					data := dataMan.GetData()
					md5s = append(md5s, dataMan.GetMD5())
					tmpfn := filepath.Join(dir, "data_"+strconv.Itoa(i)+".dat")
					err = ioutil.WriteFile(tmpfn, data, 0777)
					Expect(err).ToNot(HaveOccurred())
					copyFileToDocker(tmpfn)
				}

				err = wd.Get("https://quic.clemente.io/uploadform?num=" + strconv.Itoa(num))
				Expect(err).NotTo(HaveOccurred())

				for i := 0; i < num; i++ {
					var elem selenium.WebElement
					elem, err = wd.FindElement(selenium.ByCSSSelector, "#upload_"+strconv.Itoa(i))
					Expect(err).ToNot(HaveOccurred())
					err = elem.SendKeys("/home/seluser/data_" + strconv.Itoa(i) + ".dat")
					Expect(err).ToNot(HaveOccurred())
				}

				elem, err := wd.FindElement(selenium.ByCSSSelector, "#form")
				Expect(err).ToNot(HaveOccurred())
				Eventually(func() error { return elem.Submit() }, 30, 0.1).ShouldNot(HaveOccurred())

				for i := 0; i < num; i++ {
					file := filepath.Join(uploadDir, "data_"+strconv.Itoa(i)+".dat")
					Expect(getFileSize(file)).To(Equal(dataLen))
					Expect(getFileMD5(file)).To(Equal(md5s[i]))
				}
			})
		})
	}
})
<|endoftext|>


<commit_before>// Package constants has Kubernetes label and annotation constants shared by
// klocksmith and klocksmith-controller.
package constants

const (
	LabelPrefix           = "alpha.coreos.com/update1."
	LabelRebootNeeded     = LabelPrefix + "reboot-needed"
	LabelRebootInProgress = LabelPrefix + "reboot-in-progress"
	LabelOkToReboot       = LabelPrefix + "reboot-ok"

	AnnotationPrefix  = LabelPrefix
	AnnotationID      = AnnotationPrefix + "id"
	AnnotationGroup   = AnnotationPrefix + "group"
	AnnotationVersion = AnnotationPrefix + "version"
)
<commit_msg>internal/constants: update annotation/label prefix<commit_after>// Package constants has Kubernetes label and annotation constants shared by
// klocksmith and klocksmith-controller.
package constants

const (
	LabelPrefix           = "container-linux-update.v1.coreos.com/"
	LabelRebootNeeded     = LabelPrefix + "reboot-needed"
	LabelRebootInProgress = LabelPrefix + "reboot-in-progress"
	LabelOkToReboot       = LabelPrefix + "reboot-ok"

	AnnotationPrefix  = LabelPrefix
	AnnotationID      = AnnotationPrefix + "id"
	AnnotationGroup   = AnnotationPrefix + "group"
	AnnotationVersion = AnnotationPrefix + "version"
)
<|endoftext|>


<commit_before>package edgectl

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/datawire/ambassador/pkg/k8s"
	"github.com/datawire/ambassador/pkg/supervisor"
)

var simpleTransport = &http.Transport{
	// #nosec G402
	TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	Proxy:           nil,
	DialContext: (&net.Dialer{
		Timeout:   10 * time.Second,
		KeepAlive: 1 * time.Second,
		DualStack: true,
	}).DialContext,
	DisableKeepAlives: true,
}

var hClient = &http.Client{
	Transport: simpleTransport,
	Timeout:   15 * time.Second,
}

// Connect the daemon to a cluster
func (d *Daemon) Connect(
	p *supervisor.Process, out *Emitter, rai *RunAsInfo,
	context, namespace, managerNs string, kargs []string,
	installID string, isCI bool,
) error {
	// Sanity checks
	if d.cluster != nil {
		out.Println("Already connected")
		out.Send("connect", "Already connected")
		return nil
	}
	if d.bridge != nil {
		out.Println("Not ready: Trying to disconnect")
		out.Send("connect", "Not ready: Trying to disconnect")
		return nil
	}
	if d.network == nil {
		out.Println("Not ready: Network overrides are paused (use \"edgectl resume\")")
		out.Send("connect", "Not ready: Paused")
		return nil
	}
	if !d.network.IsOkay() {
		out.Println("Not ready: Establishing network overrides")
		out.Send("connect", "Not ready: Establishing network overrides")
		return nil
	}

	out.Printf("Connecting to traffic manager in namespace %s...\n", managerNs)
	out.Send("connect", "Connecting...")
	cluster, err := TrackKCluster(p, rai, context, namespace, kargs)
	if err != nil {
		out.Println(err.Error())
		out.Send("failed", err.Error())
		out.SendExit(1)
		return nil
	}
	d.cluster = cluster

	previewHost, err := getClusterPreviewHostname(p, cluster)
	if err != nil {
		p.Logf("get preview URL hostname: %+v", err)
		previewHost = ""
	}

	bridge, err := CheckedRetryingCommand(
		p,
		"bridge",
		[]string{GetExe(), "teleproxy", "bridge", cluster.context, cluster.namespace},
		rai,
		checkBridge,
		15*time.Second,
	)
	if err != nil {
		out.Println(err.Error())
		out.Send("failed", err.Error())
		out.SendExit(1)
		d.cluster.Close()
		d.cluster = nil
		return nil
	}
	d.bridge = bridge
	d.cluster.SetBridgeCheck(d.bridge.IsOkay)

	out.Printf(
		"Connected to context %s (%s)\n", d.cluster.Context(), d.cluster.Server(),
	)
	out.Send("cluster.context", d.cluster.Context())
	out.Send("cluster.server", d.cluster.Server())

	tmgr, err := NewTrafficManager(p, d.cluster, managerNs, installID, isCI)
	if err != nil {
		out.Println()
		out.Println("Unable to connect to the traffic manager in your cluster.")
		out.Println("The intercept feature will not be available.")
		out.Println("Error was:", err)
		// out.Println("Use <some command> to set up the traffic manager.") // FIXME
		out.Send("intercept", false)
	} else {
		tmgr.previewHost = previewHost
		d.trafficMgr = tmgr
		out.Send("intercept", true)
	}
	return nil
}

// Disconnect from the connected cluster
func (d *Daemon) Disconnect(p *supervisor.Process, out *Emitter) error {
	// Sanity checks
	if d.cluster == nil {
		out.Println("Not connected (use 'edgectl connect' to connect to your cluster)")
		out.Send("disconnect", "Not connected")
		return nil
	}

	_ = d.ClearIntercepts(p)
	if d.bridge != nil {
		d.cluster.SetBridgeCheck(nil) // Stop depending on this bridge
		_ = d.bridge.Close()
		d.bridge = nil
	}
	if d.trafficMgr != nil {
		_ = d.trafficMgr.Close()
		d.trafficMgr = nil
	}
	err := d.cluster.Close()
	d.cluster = nil

	out.Println("Disconnected")
	out.Send("disconnect", "Disconnected")
	return err
}

// getClusterPreviewHostname returns the hostname of the first Host resource it
// finds that has Preview URLs enabled with a supported URL type.
func getClusterPreviewHostname(p *supervisor.Process, cluster *KCluster) (hostname string, err error) {
	p.Log("Looking for a Host with Preview URLs enabled")

	// kubectl get hosts, in all namespaces or in this namespace
	var outBytes []byte
	outBytes, err = func() ([]byte, error) {
		clusterCmd := cluster.GetKubectlCmdNoNamespace(p, "get", "host", "-o", "yaml", "--all-namespaces")
		if outBytes, err := clusterCmd.CombinedOutput(); err == nil {
			return outBytes, nil
		}

		nsCmd := cluster.GetKubectlCmd(p, "get", "host", "-o", "yaml")
		if outBytes, err := nsCmd.CombinedOutput(); err == nil {
			return outBytes, nil
		} else {
			return nil, err
		}
	}()
	if err != nil {
		return
	}

	// Parse the output
	hostLists, kerr := k8s.ParseResources("get hosts", string(outBytes))
	if kerr != nil {
		err = kerr
		return
	}
	if len(hostLists) != 1 {
		err = errors.Errorf("weird result with length %d", len(hostLists))
		return
	}

	// Grab the "items" slice, as the result should be a list of Host resources
	hostItems := k8s.Map(hostLists[0]).GetMaps("items")
	p.Logf("Found %d Host resources", len(hostItems))

	// Loop over Hosts looking for a Preview URL hostname
	for _, hostItem := range hostItems {
		host := k8s.Resource(hostItem)
		logEntry := fmt.Sprintf("- Host %s / %s: %%s", host.Namespace(), host.Name())

		previewUrlSpec := host.Spec().GetMap("previewUrl")
		if len(previewUrlSpec) == 0 {
			p.Logf(logEntry, "no preview URL config")
			continue
		}

		if enabled, ok := previewUrlSpec["enabled"].(bool); !ok || !enabled {
			p.Logf(logEntry, "preview URL not enabled")
			continue
		}

		// missing type, default is "Path" --> success
		// type is present, set to "Path" --> success
		// otherwise --> failure
		if pType, ok := previewUrlSpec["type"].(string); ok && pType != "Path" {
			p.Logf(logEntry+": %#v", "unsupported preview URL type", previewUrlSpec["type"])
			continue
		}

		if hostname = host.Spec().GetString("hostname"); hostname == "" {
			p.Logf(logEntry, "empty hostname???")
			continue
		}

		p.Logf(logEntry+": %q", "SUCCESS! Hostname is", hostname)
		return
	}

	p.Logf("No appropriate Host resource found.")
	return
}

// checkBridge checks the status of teleproxy bridge by doing the equivalent of
//  curl http://traffic-proxy.svc:8022.
// Note there is no namespace specified, as we are checking for bridge status in the
// current namespace.
func checkBridge(p *supervisor.Process) error {
	address := "traffic-proxy.svc:8022"
	conn, err := net.DialTimeout("tcp", address, 15*time.Second)
	if err != nil {
		return errors.Wrap(err, "tcp connect")
	}
	if conn != nil {
		defer conn.Close()
		msg, _, err := bufio.NewReader(conn).ReadLine()
		if err != nil {
			return errors.Wrap(err, "tcp read")
		}
		if !strings.Contains(string(msg), "SSH") {
			return fmt.Errorf("expected SSH prompt, got: %v", string(msg))
		}
	} else {
		return fmt.Errorf("fail to establish tcp connection to %v", address)
	}
	return nil
}

// TrafficManager is a handle to access the Traffic Manager in a
// cluster.
type TrafficManager struct {
	crc            Resource
	apiPort        int
	sshPort        int
	namespace      string
	interceptables []string
	totalClusCepts int
	snapshotSent   bool
	installID      string // edgectl's install ID
	connectCI      bool   // whether --ci was passed to connect
	apiErr         error  // holds the latest traffic-manager API error
	licenseInfo    string // license information from traffic-manager
	previewHost    string // hostname to use for preview URLs, if enabled
}

// NewTrafficManager returns a TrafficManager resource for the given
// cluster if it has a Traffic Manager service.
func NewTrafficManager(p *supervisor.Process, cluster *KCluster, managerNs string, installID string, isCI bool) (*TrafficManager, error) {
	cmd := cluster.GetKubectlCmd(p, "get", "-n", managerNs, "svc/telepresence-proxy", "deploy/telepresence-proxy")
	err := cmd.Run()
	if err != nil {
		return nil, errors.Wrap(err, "kubectl get svc/deploy telepresency-proxy")
	}

	apiPort, err := GetFreePort()
	if err != nil {
		return nil, errors.Wrap(err, "get free port for API")
	}
	sshPort, err := GetFreePort()
	if err != nil {
		return nil, errors.Wrap(err, "get free port for ssh")
	}
	kpfArgStr := fmt.Sprintf("port-forward -n %s svc/telepresence-proxy %d:8022 %d:8081", managerNs, sshPort, apiPort)
	kpfArgs := cluster.GetKubectlArgs(strings.Fields(kpfArgStr)...)
	tm := &TrafficManager{
		apiPort:   apiPort,
		sshPort:   sshPort,
		namespace: managerNs,
		installID: installID,
		connectCI: isCI,
	}

	pf, err := CheckedRetryingCommand(p, "traffic-kpf", kpfArgs, cluster.RAI(), tm.check, 15*time.Second)
	if err != nil {
		return nil, err
	}
	tm.crc = pf
	return tm, nil
}

func (tm *TrafficManager) check(p *supervisor.Process) error {
	body, code, err := tm.request("GET", "state", []byte{})
	if err != nil {
		return err
	}

	if code != http.StatusOK {
		tm.apiErr = fmt.Errorf("%v: %v", code, body)
		return tm.apiErr
	}
	tm.apiErr = nil

	var state map[string]interface{}
	if err := json.Unmarshal([]byte(body), &state); err != nil {
		p.Logf("check: bad JSON from tm: %v", err)
		p.Logf("check: JSON data is: %q", body)
		return err
	}
	if licenseInfo, ok := state["LicenseInfo"]; ok {
		tm.licenseInfo = licenseInfo.(string)
	}
	deployments, ok := state["Deployments"].(map[string]interface{})
	if !ok {
		p.Log("check: failed to get deployment info")
		p.Logf("check: JSON data is: %q", body)
	}
	tm.interceptables = make([]string, len(deployments))
	tm.totalClusCepts = 0
	idx := 0
	for deployment := range deployments {
		tm.interceptables[idx] = deployment
		idx++
		info, ok := deployments[deployment].(map[string]interface{})
		if !ok {
			continue
		}
		cepts, ok := info["Intercepts"].([]interface{})
		if ok {
			tm.totalClusCepts += len(cepts)
		}
	}

	if !tm.snapshotSent {
		p.Log("trying to send snapshot")
		tm.snapshotSent = true // don't try again, even if this fails
		body, code, err := tm.request("GET", "snapshot", []byte{})
		if err != nil || code != 200 {
			p.Logf("snapshot request failed: %v", err)
			return nil
		}
		resp, err := hClient.Post("http://teleproxy/api/tables/", "application/json", strings.NewReader(body))
		if err != nil {
			p.Logf("snapshot post failed: %v", err)
			return nil
		}
		_, _ = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		p.Log("snapshot sent!")
	}

	return nil
}

func (tm *TrafficManager) request(method, path string, data []byte) (result string, code int, err error) {
	url := fmt.Sprintf("http://127.0.0.1:%d/%s", tm.apiPort, path)
	req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
	if err != nil {
		return
	}

	req.Header.Set("edgectl-install-id", tm.installID)
	req.Header.Set("edgectl-connect-ci", strconv.FormatBool(tm.connectCI))

	resp, err := hClient.Do(req)
	if err != nil {
		err = errors.Wrap(err, "get")
		return
	}
	defer resp.Body.Close()
	code = resp.StatusCode
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		err = errors.Wrap(err, "read body")
		return
	}
	result = string(body)
	return
}

// Name implements Resource
func (tm *TrafficManager) Name() string {
	return "trafficMgr"
}

// IsOkay implements Resource
func (tm *TrafficManager) IsOkay() bool {
	return tm.crc.IsOkay()
}

// Close implements Resource
func (tm *TrafficManager) Close() error {
	return tm.crc.Close()
}
<commit_msg>(from AES) Fixing preview url Host lookup as previewUrl.type enum is lowercase<commit_after>package edgectl

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/datawire/ambassador/pkg/k8s"
	"github.com/datawire/ambassador/pkg/supervisor"
)

var simpleTransport = &http.Transport{
	// #nosec G402
	TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	Proxy:           nil,
	DialContext: (&net.Dialer{
		Timeout:   10 * time.Second,
		KeepAlive: 1 * time.Second,
		DualStack: true,
	}).DialContext,
	DisableKeepAlives: true,
}

var hClient = &http.Client{
	Transport: simpleTransport,
	Timeout:   15 * time.Second,
}

// Connect the daemon to a cluster
func (d *Daemon) Connect(
	p *supervisor.Process, out *Emitter, rai *RunAsInfo,
	context, namespace, managerNs string, kargs []string,
	installID string, isCI bool,
) error {
	// Sanity checks
	if d.cluster != nil {
		out.Println("Already connected")
		out.Send("connect", "Already connected")
		return nil
	}
	if d.bridge != nil {
		out.Println("Not ready: Trying to disconnect")
		out.Send("connect", "Not ready: Trying to disconnect")
		return nil
	}
	if d.network == nil {
		out.Println("Not ready: Network overrides are paused (use \"edgectl resume\")")
		out.Send("connect", "Not ready: Paused")
		return nil
	}
	if !d.network.IsOkay() {
		out.Println("Not ready: Establishing network overrides")
		out.Send("connect", "Not ready: Establishing network overrides")
		return nil
	}

	out.Printf("Connecting to traffic manager in namespace %s...\n", managerNs)
	out.Send("connect", "Connecting...")
	cluster, err := TrackKCluster(p, rai, context, namespace, kargs)
	if err != nil {
		out.Println(err.Error())
		out.Send("failed", err.Error())
		out.SendExit(1)
		return nil
	}
	d.cluster = cluster

	previewHost, err := getClusterPreviewHostname(p, cluster)
	if err != nil {
		p.Logf("get preview URL hostname: %+v", err)
		previewHost = ""
	}

	bridge, err := CheckedRetryingCommand(
		p,
		"bridge",
		[]string{GetExe(), "teleproxy", "bridge", cluster.context, cluster.namespace},
		rai,
		checkBridge,
		15*time.Second,
	)
	if err != nil {
		out.Println(err.Error())
		out.Send("failed", err.Error())
		out.SendExit(1)
		d.cluster.Close()
		d.cluster = nil
		return nil
	}
	d.bridge = bridge
	d.cluster.SetBridgeCheck(d.bridge.IsOkay)

	out.Printf(
		"Connected to context %s (%s)\n", d.cluster.Context(), d.cluster.Server(),
	)
	out.Send("cluster.context", d.cluster.Context())
	out.Send("cluster.server", d.cluster.Server())

	tmgr, err := NewTrafficManager(p, d.cluster, managerNs, installID, isCI)
	if err != nil {
		out.Println()
		out.Println("Unable to connect to the traffic manager in your cluster.")
		out.Println("The intercept feature will not be available.")
		out.Println("Error was:", err)
		// out.Println("Use <some command> to set up the traffic manager.") // FIXME
		out.Send("intercept", false)
	} else {
		tmgr.previewHost = previewHost
		d.trafficMgr = tmgr
		out.Send("intercept", true)
	}
	return nil
}

// Disconnect from the connected cluster
func (d *Daemon) Disconnect(p *supervisor.Process, out *Emitter) error {
	// Sanity checks
	if d.cluster == nil {
		out.Println("Not connected (use 'edgectl connect' to connect to your cluster)")
		out.Send("disconnect", "Not connected")
		return nil
	}

	_ = d.ClearIntercepts(p)
	if d.bridge != nil {
		d.cluster.SetBridgeCheck(nil) // Stop depending on this bridge
		_ = d.bridge.Close()
		d.bridge = nil
	}
	if d.trafficMgr != nil {
		_ = d.trafficMgr.Close()
		d.trafficMgr = nil
	}
	err := d.cluster.Close()
	d.cluster = nil

	out.Println("Disconnected")
	out.Send("disconnect", "Disconnected")
	return err
}

// getClusterPreviewHostname returns the hostname of the first Host resource it
// finds that has Preview URLs enabled with a supported URL type.
func getClusterPreviewHostname(p *supervisor.Process, cluster *KCluster) (hostname string, err error) {
	p.Log("Looking for a Host with Preview URLs enabled")

	// kubectl get hosts, in all namespaces or in this namespace
	var outBytes []byte
	outBytes, err = func() ([]byte, error) {
		clusterCmd := cluster.GetKubectlCmdNoNamespace(p, "get", "host", "-o", "yaml", "--all-namespaces")
		if outBytes, err := clusterCmd.CombinedOutput(); err == nil {
			return outBytes, nil
		}

		nsCmd := cluster.GetKubectlCmd(p, "get", "host", "-o", "yaml")
		if outBytes, err := nsCmd.CombinedOutput(); err == nil {
			return outBytes, nil
		} else {
			return nil, err
		}
	}()
	if err != nil {
		return
	}

	// Parse the output
	hostLists, kerr := k8s.ParseResources("get hosts", string(outBytes))
	if kerr != nil {
		err = kerr
		return
	}
	if len(hostLists) != 1 {
		err = errors.Errorf("weird result with length %d", len(hostLists))
		return
	}

	// Grab the "items" slice, as the result should be a list of Host resources
	hostItems := k8s.Map(hostLists[0]).GetMaps("items")
	p.Logf("Found %d Host resources", len(hostItems))

	// Loop over Hosts looking for a Preview URL hostname
	for _, hostItem := range hostItems {
		host := k8s.Resource(hostItem)
		logEntry := fmt.Sprintf("- Host %s / %s: %%s", host.Namespace(), host.Name())

		previewUrlSpec := host.Spec().GetMap("previewUrl")
		if len(previewUrlSpec) == 0 {
			p.Logf(logEntry, "no preview URL config")
			continue
		}

		if enabled, ok := previewUrlSpec["enabled"].(bool); !ok || !enabled {
			p.Logf(logEntry, "preview URL not enabled")
			continue
		}

		// missing type, default is "path" --> success
		// type is present, set to "path" --> success
		// otherwise --> failure
		if pType, ok := previewUrlSpec["type"].(string); ok && pType != "path" {
			p.Logf(logEntry+": %#v", "unsupported preview URL type", previewUrlSpec["type"])
			continue
		}

		if hostname = host.Spec().GetString("hostname"); hostname == "" {
			p.Logf(logEntry, "empty hostname???")
			continue
		}

		p.Logf(logEntry+": %q", "SUCCESS! Hostname is", hostname)
		return
	}

	p.Logf("No appropriate Host resource found.")
	return
}

// checkBridge checks the status of teleproxy bridge by doing the equivalent of
//  curl http://traffic-proxy.svc:8022.
// Note there is no namespace specified, as we are checking for bridge status in the
// current namespace.
func checkBridge(p *supervisor.Process) error {
	address := "traffic-proxy.svc:8022"
	conn, err := net.DialTimeout("tcp", address, 15*time.Second)
	if err != nil {
		return errors.Wrap(err, "tcp connect")
	}
	if conn != nil {
		defer conn.Close()
		msg, _, err := bufio.NewReader(conn).ReadLine()
		if err != nil {
			return errors.Wrap(err, "tcp read")
		}
		if !strings.Contains(string(msg), "SSH") {
			return fmt.Errorf("expected SSH prompt, got: %v", string(msg))
		}
	} else {
		return fmt.Errorf("fail to establish tcp connection to %v", address)
	}
	return nil
}

// TrafficManager is a handle to access the Traffic Manager in a
// cluster.
type TrafficManager struct {
	crc            Resource
	apiPort        int
	sshPort        int
	namespace      string
	interceptables []string
	totalClusCepts int
	snapshotSent   bool
	installID      string // edgectl's install ID
	connectCI      bool   // whether --ci was passed to connect
	apiErr         error  // holds the latest traffic-manager API error
	licenseInfo    string // license information from traffic-manager
	previewHost    string // hostname to use for preview URLs, if enabled
}

// NewTrafficManager returns a TrafficManager resource for the given
// cluster if it has a Traffic Manager service.
func NewTrafficManager(p *supervisor.Process, cluster *KCluster, managerNs string, installID string, isCI bool) (*TrafficManager, error) {
	cmd := cluster.GetKubectlCmd(p, "get", "-n", managerNs, "svc/telepresence-proxy", "deploy/telepresence-proxy")
	err := cmd.Run()
	if err != nil {
		return nil, errors.Wrap(err, "kubectl get svc/deploy telepresency-proxy")
	}

	apiPort, err := GetFreePort()
	if err != nil {
		return nil, errors.Wrap(err, "get free port for API")
	}
	sshPort, err := GetFreePort()
	if err != nil {
		return nil, errors.Wrap(err, "get free port for ssh")
	}
	kpfArgStr := fmt.Sprintf("port-forward -n %s svc/telepresence-proxy %d:8022 %d:8081", managerNs, sshPort, apiPort)
	kpfArgs := cluster.GetKubectlArgs(strings.Fields(kpfArgStr)...)
	tm := &TrafficManager{
		apiPort:   apiPort,
		sshPort:   sshPort,
		namespace: managerNs,
		installID: installID,
		connectCI: isCI,
	}

	pf, err := CheckedRetryingCommand(p, "traffic-kpf", kpfArgs, cluster.RAI(), tm.check, 15*time.Second)
	if err != nil {
		return nil, err
	}
	tm.crc = pf
	return tm, nil
}

func (tm *TrafficManager) check(p *supervisor.Process) error {
	body, code, err := tm.request("GET", "state", []byte{})
	if err != nil {
		return err
	}

	if code != http.StatusOK {
		tm.apiErr = fmt.Errorf("%v: %v", code, body)
		return tm.apiErr
	}
	tm.apiErr = nil

	var state map[string]interface{}
	if err := json.Unmarshal([]byte(body), &state); err != nil {
		p.Logf("check: bad JSON from tm: %v", err)
		p.Logf("check: JSON data is: %q", body)
		return err
	}
	if licenseInfo, ok := state["LicenseInfo"]; ok {
		tm.licenseInfo = licenseInfo.(string)
	}
	deployments, ok := state["Deployments"].(map[string]interface{})
	if !ok {
		p.Log("check: failed to get deployment info")
		p.Logf("check: JSON data is: %q", body)
	}
	tm.interceptables = make([]string, len(deployments))
	tm.totalClusCepts = 0
	idx := 0
	for deployment := range deployments {
		tm.interceptables[idx] = deployment
		idx++
		info, ok := deployments[deployment].(map[string]interface{})
		if !ok {
			continue
		}
		cepts, ok := info["Intercepts"].([]interface{})
		if ok {
			tm.totalClusCepts += len(cepts)
		}
	}

	if !tm.snapshotSent {
		p.Log("trying to send snapshot")
		tm.snapshotSent = true // don't try again, even if this fails
		body, code, err := tm.request("GET", "snapshot", []byte{})
		if err != nil || code != 200 {
			p.Logf("snapshot request failed: %v", err)
			return nil
		}
		resp, err := hClient.Post("http://teleproxy/api/tables/", "application/json", strings.NewReader(body))
		if err != nil {
			p.Logf("snapshot post failed: %v", err)
			return nil
		}
		_, _ = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		p.Log("snapshot sent!")
	}

	return nil
}

func (tm *TrafficManager) request(method, path string, data []byte) (result string, code int, err error) {
	url := fmt.Sprintf("http://127.0.0.1:%d/%s", tm.apiPort, path)
	req, err := http.NewRequest(method, url, bytes.NewBuffer(data))
	if err != nil {
		return
	}

	req.Header.Set("edgectl-install-id", tm.installID)
	req.Header.Set("edgectl-connect-ci", strconv.FormatBool(tm.connectCI))

	resp, err := hClient.Do(req)
	if err != nil {
		err = errors.Wrap(err, "get")
		return
	}
	defer resp.Body.Close()
	code = resp.StatusCode
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		err = errors.Wrap(err, "read body")
		return
	}
	result = string(body)
	return
}

// Name implements Resource
func (tm *TrafficManager) Name() string {
	return "trafficMgr"
}

// IsOkay implements Resource
func (tm *TrafficManager) IsOkay() bool {
	return tm.crc.IsOkay()
}

// Close implements Resource
func (tm *TrafficManager) Close() error {
	return tm.crc.Close()
}
<|endoftext|>


<commit_before>package plugins

import (
    "github.com/bwmarrin/discordgo"
    "github.com/dustin/go-humanize"
    "git.lukas.moe/sn0w/Karen/version"
    "runtime"
    "strconv"
    "fmt"
    "git.lukas.moe/sn0w/Karen/metrics"
    "time"
)

type Stats struct{}

func (s Stats) Commands() []string {
    return []string{
        "stats",
    }
}

func (s Stats) Init(session *discordgo.Session) {

}

func (s Stats) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
    // Count guilds, channels and users
    users := make(map[string]string)
    channels := 0
    guilds := session.State.Guilds

    for _, guild := range guilds {
        channels += len(guild.Channels)

        for _, u := range guild.Members {
            users[u.User.ID] = u.User.Username
        }
    }

    // Get RAM stats
    var ram runtime.MemStats
    runtime.ReadMemStats(&ram)

    // Get uptiume
    bootTime, err := strconv.ParseInt(metrics.Uptime.String(), 10, 64)
    if err != nil {
        bootTime = 0
    }

    uptime := time.Now().Sub(time.Unix(bootTime, 0)).String()

    session.ChannelMessageSendEmbed(msg.ChannelID, &discordgo.MessageEmbed{
        Color: 0x0FADED,
        Thumbnail: &discordgo.MessageEmbedThumbnail{
            URL: fmt.Sprintf(
                "https://cdn.discordapp.com/avatars/%s/%s.jpg",
                session.State.User.ID,
                session.State.User.Avatar,
            ),
        },
        Fields: []*discordgo.MessageEmbedField{
            // Build
            {Name: "Build Time", Value: version.BUILD_TIME, Inline: false},
            {Name: "Build System", Value: version.BUILD_USER + "@" + version.BUILD_HOST, Inline: false},

            // System
            {Name: "Bot Uptime", Value: uptime, Inline: true},
            {Name: "Bot Version", Value: version.BOT_VERSION, Inline: true},
            {Name: "GO Version", Value: runtime.Version(), Inline: true},

            // Bot
            {Name: "Used RAM", Value: humanize.Bytes(ram.Alloc) + "/" + humanize.Bytes(ram.Sys), Inline: true},
            {Name: "Collected garbage", Value: humanize.Bytes(ram.TotalAlloc), Inline: true},
            {Name: "Running coroutines", Value: strconv.Itoa(runtime.NumGoroutine()), Inline: true},

            // Discord
            {Name: "Connected servers", Value: strconv.Itoa(len(guilds)), Inline: true},
            {Name: "Watching channels", Value: strconv.Itoa(channels), Inline: true},
            {Name: "Users with access to me", Value: strconv.Itoa(len(users)), Inline: true},

            // Link
            {Name: "Want more stats and awesome graphs?", Value: "Visit [stats.meetkaren.xyz](https://stats.meetkaren.xyz)", Inline: false},
        },
    })
}
<commit_msg>Add !sys alias | Closes #28<commit_after>package plugins

import (
    "github.com/bwmarrin/discordgo"
    "github.com/dustin/go-humanize"
    "git.lukas.moe/sn0w/Karen/version"
    "runtime"
    "strconv"
    "fmt"
    "git.lukas.moe/sn0w/Karen/metrics"
    "time"
)

type Stats struct{}

func (s Stats) Commands() []string {
    return []string{
        "stats",
        "sys",
    }
}

func (s Stats) Init(session *discordgo.Session) {

}

func (s Stats) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) {
    // Count guilds, channels and users
    users := make(map[string]string)
    channels := 0
    guilds := session.State.Guilds

    for _, guild := range guilds {
        channels += len(guild.Channels)

        for _, u := range guild.Members {
            users[u.User.ID] = u.User.Username
        }
    }

    // Get RAM stats
    var ram runtime.MemStats
    runtime.ReadMemStats(&ram)

    // Get uptiume
    bootTime, err := strconv.ParseInt(metrics.Uptime.String(), 10, 64)
    if err != nil {
        bootTime = 0
    }

    uptime := time.Now().Sub(time.Unix(bootTime, 0)).String()

    session.ChannelMessageSendEmbed(msg.ChannelID, &discordgo.MessageEmbed{
        Color: 0x0FADED,
        Thumbnail: &discordgo.MessageEmbedThumbnail{
            URL: fmt.Sprintf(
                "https://cdn.discordapp.com/avatars/%s/%s.jpg",
                session.State.User.ID,
                session.State.User.Avatar,
            ),
        },
        Fields: []*discordgo.MessageEmbedField{
            // Build
            {Name: "Build Time", Value: version.BUILD_TIME, Inline: false},
            {Name: "Build System", Value: version.BUILD_USER + "@" + version.BUILD_HOST, Inline: false},

            // System
            {Name: "Bot Uptime", Value: uptime, Inline: true},
            {Name: "Bot Version", Value: version.BOT_VERSION, Inline: true},
            {Name: "GO Version", Value: runtime.Version(), Inline: true},

            // Bot
            {Name: "Used RAM", Value: humanize.Bytes(ram.Alloc) + "/" + humanize.Bytes(ram.Sys), Inline: true},
            {Name: "Collected garbage", Value: humanize.Bytes(ram.TotalAlloc), Inline: true},
            {Name: "Running coroutines", Value: strconv.Itoa(runtime.NumGoroutine()), Inline: true},

            // Discord
            {Name: "Connected servers", Value: strconv.Itoa(len(guilds)), Inline: true},
            {Name: "Watching channels", Value: strconv.Itoa(channels), Inline: true},
            {Name: "Users with access to me", Value: strconv.Itoa(len(users)), Inline: true},

            // Link
            {Name: "Want more stats and awesome graphs?", Value: "Visit [stats.meetkaren.xyz](https://stats.meetkaren.xyz)", Inline: false},
        },
    })
}
<|endoftext|>


<commit_before>// Copyright (C) 2018 Nippon Telegraph and Telephone Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package version

import "fmt"

const MAJOR uint = 3
const MINOR uint = 0
const PATCH uint = 0

var COMMIT string = ""
var IDENTIFIER string = ""
var METADATA string = ""

func Version() string {
	var suffix string = ""
	if len(IDENTIFIER) > 0 {
		suffix = fmt.Sprintf("-%s", IDENTIFIER)
	}

	if len(COMMIT) > 0 || len(METADATA) > 0 {
		suffix = suffix + "+"
	}

	if len(COMMIT) > 0 {
		suffix = fmt.Sprintf("%s"+"commit.%s", suffix, COMMIT)

	}

	if len(METADATA) > 0 {
		if len(COMMIT) > 0 {
			suffix = suffix + "."
		}
		suffix = suffix + METADATA
	}

	return fmt.Sprintf("%d.%d.%d%s", MAJOR, MINOR, PATCH, suffix)
}
<commit_msg>v3.1.0<commit_after>// Copyright (C) 2018 Nippon Telegraph and Telephone Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package version

import "fmt"

const MAJOR uint = 3
const MINOR uint = 1
const PATCH uint = 0

var COMMIT string = ""
var IDENTIFIER string = ""
var METADATA string = ""

func Version() string {
	var suffix string = ""
	if len(IDENTIFIER) > 0 {
		suffix = fmt.Sprintf("-%s", IDENTIFIER)
	}

	if len(COMMIT) > 0 || len(METADATA) > 0 {
		suffix = suffix + "+"
	}

	if len(COMMIT) > 0 {
		suffix = fmt.Sprintf("%s"+"commit.%s", suffix, COMMIT)

	}

	if len(METADATA) > 0 {
		if len(COMMIT) > 0 {
			suffix = suffix + "."
		}
		suffix = suffix + METADATA
	}

	return fmt.Sprintf("%d.%d.%d%s", MAJOR, MINOR, PATCH, suffix)
}
<|endoftext|>


<commit_before>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package shareable

import (
	"fmt"
	"image"
	"image/color"
	"runtime"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/packing"
	"github.com/hajimehoshi/ebiten/internal/restorable"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

type backend struct {
	restorable *restorable.Image

	// If page is nil, the backend is not shared.
	page *packing.Page
}

func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) {
	// If the region is allocated without any extention, it's fine.
	if n := b.page.Alloc(width, height); n != nil {
		return n, true
	}

	// Simulate the extending the page and calculate the appropriate page size.
	page := b.page.Clone()
	nExtended := 0
	for {
		if !page.Extend() {
			// The page can't be extended any more. Return as failure.
			return nil, false
		}
		nExtended++
		if n := page.Alloc(width, height); n != nil {
			break
		}
	}

	for i := 0; i < nExtended; i++ {
		b.page.Extend()
	}
	s := b.page.Size()
	newImg := restorable.NewImage(s, s, false)
	oldImg := b.restorable
	w, h := oldImg.Size()
	newImg.DrawImage(oldImg, 0, 0, w, h, nil, nil, opengl.CompositeModeCopy, graphics.FilterNearest)
	oldImg.Dispose()
	b.restorable = newImg

	n := b.page.Alloc(width, height)
	if n == nil {
		panic("not reached")
	}
	return n, true
}

var (
	// backendsM is a mutex for critical sections of the backend and packing.Node objects.
	backendsM sync.Mutex

	// theBackends is a set of actually shared images.
	theBackends = []*backend{}
)

type Image struct {
	width    int
	height   int
	disposed bool

	backend *backend

	// If node is nil, the image is not shared.
	node *packing.Node
}

func (i *Image) ensureNotShared() {
	if i.backend == nil {
		i.allocate(false)
		return
	}

	if i.node == nil {
		return
	}

	x, y, w, h := i.region()
	newImg := restorable.NewImage(w, h, false)
	newImg.DrawImage(i.backend.restorable, x, y, x+w, y+h, nil, nil, opengl.CompositeModeCopy, graphics.FilterNearest)

	i.dispose()
	i.backend = &backend{
		restorable: newImg,
	}
}

func (i *Image) region() (x, y, width, height int) {
	if i.backend == nil {
		panic("not reached")
	}
	if i.node == nil {
		w, h := i.backend.restorable.Size()
		return 0, 0, w, h
	}
	return i.node.Region()
}

func (i *Image) Size() (width, height int) {
	return i.width, i.height
}

func (i *Image) DrawImage(img *Image, sx0, sy0, sx1, sy1 int, geom *affine.GeoM, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if img.backend == nil {
		img.allocate(true)
	}

	i.ensureNotShared()

	// Compare i and img after ensuring i is not shared, or
	// i and img might share the same texture even though i != img.
	if i.backend.restorable == img.backend.restorable {
		panic("shareable: Image.DrawImage: img must be different from the receiver")
	}

	dx, dy, _, _ := img.region()
	sx0 += dx
	sy0 += dy
	sx1 += dx
	sy1 += dy
	i.backend.restorable.DrawImage(img.backend.restorable, sx0, sy0, sx1, sy1, geom, colorm, mode, filter)
}

func (i *Image) ReplacePixels(p []byte) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if i.backend == nil {
		i.allocate(true)
	}

	x, y, w, h := i.region()
	if l := 4 * w * h; len(p) != l {
		panic(fmt.Sprintf("shareable: len(p) was %d but must be %d", len(p), l))
	}
	i.backend.restorable.ReplacePixels(p, x, y, w, h)
}

func (i *Image) At(x, y int) (color.Color, error) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if i.backend == nil {
		return color.RGBA{}, nil
	}

	ox, oy, w, h := i.region()
	if x < 0 || y < 0 || x >= w || y >= h {
		return color.RGBA{}, nil
	}

	clr, err := i.backend.restorable.At(x+ox, y+oy)
	return clr, err
}

func (i *Image) Dispose() {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.dispose()
}

func (i *Image) dispose() {
	defer func() {
		i.disposed = true
		i.backend = nil
		i.node = nil
		runtime.SetFinalizer(i, nil)
	}()

	if i.disposed {
		return
	}

	if i.backend == nil {
		// Not allocated yet.
		return
	}

	if i.node == nil {
		i.backend.restorable.Dispose()
		return
	}

	i.backend.page.Free(i.node)
	if !i.backend.page.IsEmpty() {
		// As this part can be reused, this should be cleared explicitly.
		x0, y0, x1, y1 := i.region()
		i.backend.restorable.ReplacePixels(nil, x0, y0, x1, y1)
		return
	}

	i.backend.restorable.Dispose()
	index := -1
	for idx, sh := range theBackends {
		if sh == i.backend {
			index = idx
			break
		}
	}
	if index == -1 {
		panic("not reached")
	}
	theBackends = append(theBackends[:index], theBackends[index+1:]...)
}

func (i *Image) IsInvalidated() (bool, error) {
	backendsM.Lock()
	defer backendsM.Unlock()
	v, err := i.backend.restorable.IsInvalidated()
	return v, err
}

func NewImage(width, height int) *Image {
	// Actual allocation is done lazily.
	return &Image{
		width:  width,
		height: height,
	}
}

func (i *Image) allocate(shareable bool) {
	if i.backend != nil {
		panic("not reached")
	}

	const (
		initSize = 1024
		maxSize  = 4096
	)

	if !shareable || i.width > maxSize || i.height > maxSize {
		i.backend = &backend{
			restorable: restorable.NewImage(i.width, i.height, false),
		}
		return
	}

	for _, b := range theBackends {
		if n, ok := b.TryAlloc(i.width, i.height); ok {
			i.backend = b
			i.node = n
			return
		}
	}
	size := initSize
	for i.width > size || i.height > size {
		if size == maxSize {
			panic("not reached")
		}
		size *= 2
	}

	b := &backend{
		restorable: restorable.NewImage(size, size, false),
		page:       packing.NewPage(size, maxSize),
	}
	theBackends = append(theBackends, b)

	n := b.page.Alloc(i.width, i.height)
	if n == nil {
		panic("not reached")
	}
	i.backend = b
	i.node = n
	runtime.SetFinalizer(i, (*Image).Dispose)
	return
}

func NewVolatileImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewImage(width, height, true)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func NewScreenFramebufferImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewScreenFramebufferImage(width, height)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func InitializeGLState() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.InitializeGLState()
}

func ResolveStaleImages() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.ResolveStaleImages()
}

func IsRestoringEnabled() bool {
	// As IsRestoringEnabled is an immutable state, no need to lock here.
	return restorable.IsRestoringEnabled()
}

func Restore() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Restore()
}

func Images() ([]image.Image, error) {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Images()
}
<commit_msg>shareable: Add checks at operations for disposed images<commit_after>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package shareable

import (
	"fmt"
	"image"
	"image/color"
	"runtime"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/packing"
	"github.com/hajimehoshi/ebiten/internal/restorable"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

type backend struct {
	restorable *restorable.Image

	// If page is nil, the backend is not shared.
	page *packing.Page
}

func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) {
	// If the region is allocated without any extention, it's fine.
	if n := b.page.Alloc(width, height); n != nil {
		return n, true
	}

	// Simulate the extending the page and calculate the appropriate page size.
	page := b.page.Clone()
	nExtended := 0
	for {
		if !page.Extend() {
			// The page can't be extended any more. Return as failure.
			return nil, false
		}
		nExtended++
		if n := page.Alloc(width, height); n != nil {
			break
		}
	}

	for i := 0; i < nExtended; i++ {
		b.page.Extend()
	}
	s := b.page.Size()
	newImg := restorable.NewImage(s, s, false)
	oldImg := b.restorable
	w, h := oldImg.Size()
	newImg.DrawImage(oldImg, 0, 0, w, h, nil, nil, opengl.CompositeModeCopy, graphics.FilterNearest)
	oldImg.Dispose()
	b.restorable = newImg

	n := b.page.Alloc(width, height)
	if n == nil {
		panic("not reached")
	}
	return n, true
}

var (
	// backendsM is a mutex for critical sections of the backend and packing.Node objects.
	backendsM sync.Mutex

	// theBackends is a set of actually shared images.
	theBackends = []*backend{}
)

type Image struct {
	width    int
	height   int
	disposed bool

	backend *backend

	// If node is nil, the image is not shared.
	node *packing.Node
}

func (i *Image) ensureNotShared() {
	if i.backend == nil {
		i.allocate(false)
		return
	}

	if i.node == nil {
		return
	}

	x, y, w, h := i.region()
	newImg := restorable.NewImage(w, h, false)
	newImg.DrawImage(i.backend.restorable, x, y, x+w, y+h, nil, nil, opengl.CompositeModeCopy, graphics.FilterNearest)

	i.dispose()
	i.backend = &backend{
		restorable: newImg,
	}
}

func (i *Image) region() (x, y, width, height int) {
	if i.backend == nil {
		panic("not reached")
	}
	if i.node == nil {
		w, h := i.backend.restorable.Size()
		return 0, 0, w, h
	}
	return i.node.Region()
}

func (i *Image) Size() (width, height int) {
	return i.width, i.height
}

func (i *Image) DrawImage(img *Image, sx0, sy0, sx1, sy1 int, geom *affine.GeoM, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if img.disposed {
		panic("shareable: the drawing source image must not be disposed")
	}
	if i.disposed {
		panic("shareable: the drawing target image must not be disposed")
	}
	if img.backend == nil {
		img.allocate(true)
	}

	i.ensureNotShared()

	// Compare i and img after ensuring i is not shared, or
	// i and img might share the same texture even though i != img.
	if i.backend.restorable == img.backend.restorable {
		panic("shareable: Image.DrawImage: img must be different from the receiver")
	}

	dx, dy, _, _ := img.region()
	sx0 += dx
	sy0 += dy
	sx1 += dx
	sy1 += dy
	i.backend.restorable.DrawImage(img.backend.restorable, sx0, sy0, sx1, sy1, geom, colorm, mode, filter)
}

func (i *Image) ReplacePixels(p []byte) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if i.disposed {
		panic("shareable: the image must not be disposed")
	}
	if i.backend == nil {
		i.allocate(true)
	}

	x, y, w, h := i.region()
	if l := 4 * w * h; len(p) != l {
		panic(fmt.Sprintf("shareable: len(p) was %d but must be %d", len(p), l))
	}
	i.backend.restorable.ReplacePixels(p, x, y, w, h)
}

func (i *Image) At(x, y int) (color.Color, error) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if i.backend == nil {
		return color.RGBA{}, nil
	}

	ox, oy, w, h := i.region()
	if x < 0 || y < 0 || x >= w || y >= h {
		return color.RGBA{}, nil
	}

	clr, err := i.backend.restorable.At(x+ox, y+oy)
	return clr, err
}

func (i *Image) Dispose() {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.dispose()
}

func (i *Image) dispose() {
	defer func() {
		i.disposed = true
		i.backend = nil
		i.node = nil
		runtime.SetFinalizer(i, nil)
	}()

	if i.disposed {
		return
	}

	if i.backend == nil {
		// Not allocated yet.
		return
	}

	if i.node == nil {
		i.backend.restorable.Dispose()
		return
	}

	i.backend.page.Free(i.node)
	if !i.backend.page.IsEmpty() {
		// As this part can be reused, this should be cleared explicitly.
		x0, y0, x1, y1 := i.region()
		i.backend.restorable.ReplacePixels(nil, x0, y0, x1, y1)
		return
	}

	i.backend.restorable.Dispose()
	index := -1
	for idx, sh := range theBackends {
		if sh == i.backend {
			index = idx
			break
		}
	}
	if index == -1 {
		panic("not reached")
	}
	theBackends = append(theBackends[:index], theBackends[index+1:]...)
}

func (i *Image) IsInvalidated() (bool, error) {
	backendsM.Lock()
	defer backendsM.Unlock()
	v, err := i.backend.restorable.IsInvalidated()
	return v, err
}

func NewImage(width, height int) *Image {
	// Actual allocation is done lazily.
	return &Image{
		width:  width,
		height: height,
	}
}

func (i *Image) allocate(shareable bool) {
	if i.backend != nil {
		panic("not reached")
	}

	const (
		initSize = 1024
		maxSize  = 4096
	)

	if !shareable || i.width > maxSize || i.height > maxSize {
		i.backend = &backend{
			restorable: restorable.NewImage(i.width, i.height, false),
		}
		return
	}

	for _, b := range theBackends {
		if n, ok := b.TryAlloc(i.width, i.height); ok {
			i.backend = b
			i.node = n
			return
		}
	}
	size := initSize
	for i.width > size || i.height > size {
		if size == maxSize {
			panic("not reached")
		}
		size *= 2
	}

	b := &backend{
		restorable: restorable.NewImage(size, size, false),
		page:       packing.NewPage(size, maxSize),
	}
	theBackends = append(theBackends, b)

	n := b.page.Alloc(i.width, i.height)
	if n == nil {
		panic("not reached")
	}
	i.backend = b
	i.node = n
	runtime.SetFinalizer(i, (*Image).Dispose)
	return
}

func NewVolatileImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewImage(width, height, true)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func NewScreenFramebufferImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewScreenFramebufferImage(width, height)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func InitializeGLState() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.InitializeGLState()
}

func ResolveStaleImages() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.ResolveStaleImages()
}

func IsRestoringEnabled() bool {
	// As IsRestoringEnabled is an immutable state, no need to lock here.
	return restorable.IsRestoringEnabled()
}

func Restore() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Restore()
}

func Images() ([]image.Image, error) {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Images()
}
<|endoftext|>