<commit_before>package mail_test

import (
	"bytes"
	"errors"
	"log"
	"net"
	"net/smtp"
	"strings"
	"time"

	"github.com/cloudfoundry-incubator/notifications/mail"

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

var _ = Describe("Mail", func() {
	var mailServer *SMTPServer
	var client *mail.Client
	var logger *log.Logger
	var buffer *bytes.Buffer
	var config mail.Config

	BeforeEach(func() {
		var err error

		buffer = bytes.NewBuffer([]byte{})
		logger = log.New(buffer, "", 0)
		mailServer = NewSMTPServer("user", "pass")

		config = mail.Config{
			User:          "user",
			Pass:          "pass",
			TestMode:      false,
			SkipVerifySSL: true,
			DisableTLS:    false,
		}

		config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
		if err != nil {
			panic(err)
		}

		client = mail.NewClient(config, logger)
	})

	AfterEach(func() {
		mailServer.Close()
	})

	Describe("NewClient", func() {
		It("defaults the ConnectTimeout to 15 seconds", func() {
			config.ConnectTimeout = 0

			client = mail.NewClient(config, logger)

			Expect(client.ConnectTimeout()).To(Equal(15 * time.Second))
		})
	})

	Describe("Send", func() {
		Context("when in Testmode", func() {
			var msg mail.Message

			BeforeEach(func() {
				var err error

				mailServer.SupportsTLS = true
				config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
				if err != nil {
					panic(err)
				}

				config.TestMode = true
				client = mail.NewClient(config, logger)

				msg = mail.Message{
					From:    "me@example.com",
					To:      "you@example.com",
					Subject: "Urgent! Read now!",
					Body: []mail.Part{
						{
							ContentType: "text/plain",
							Content:     "This email is the most important thing you will read all day!",
						},
					},
				}
			})

			It("does not connect to the smtp server", func() {
				err := client.Send(msg)
				if err != nil {
					panic(err)
				}

				Eventually(func() int {
					return len(mailServer.Deliveries)
				}).Should(Equal(0))
			})

			It("logs that it is in test mode", func() {
				err := client.Send(msg)
				if err != nil {
					panic(err)
				}

				line, err := buffer.ReadString('\n')
				if err != nil {
					panic(err)
				}

				Expect(line).To(Equal("TEST_MODE is enabled, emails not being sent\n"))
			})
		})
	})

	Context("when the SMTP server is properly configured", func() {
		BeforeEach(func() {
			var err error

			mailServer.SupportsTLS = true
			config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
			if err != nil {
				panic(err)
			}
			client = mail.NewClient(config, logger)
		})

		It("can send mail", func() {
			msg := mail.Message{
				From:    "me@example.com",
				To:      "you@example.com",
				Subject: "Urgent! Read now!",
				Body: []mail.Part{
					{
						ContentType: "text/plain",
						Content:     "This email is the most important thing you will read all day%40!",
					},
				},
			}

			err := client.Send(msg)
			if err != nil {
				panic(err)
			}

			Eventually(func() int {
				return len(mailServer.Deliveries)
			}).Should(Equal(1))
			delivery := mailServer.Deliveries[0]

			Expect(delivery.Sender).To(Equal("me@example.com"))
			Expect(delivery.Recipient).To(Equal("you@example.com"))
			Expect(delivery.Data).To(Equal(strings.Split(msg.Data(), "\n")))
			Expect(delivery.UsedTLS).To(BeTrue())
		})

		It("can make multiple requests", func() {
			firstMsg := mail.Message{
				From:    "me@example.com",
				To:      "you@example.com",
				Subject: "Urgent! Read now!",
				Body: []mail.Part{
					{
						ContentType: "text/plain",
						Content:     "This email is the most important thing you will read all day!",
					},
				},
			}

			err := client.Send(firstMsg)
			if err != nil {
				panic(err)
			}

			Eventually(func() int {
				return len(mailServer.Deliveries)
			}).Should(Equal(1))
			delivery := mailServer.Deliveries[0]

			Expect(delivery.Sender).To(Equal("me@example.com"))
			Expect(delivery.Recipient).To(Equal("you@example.com"))
			Expect(delivery.Data).To(Equal(strings.Split(firstMsg.Data(), "\n")))

			secondMsg := mail.Message{
				From:    "first@example.com",
				To:      "second@example.com",
				Subject: "Boring. Do not read.",
				Body: []mail.Part{
					{
						ContentType: "text/plain",
						Content:     "This email is the least important thing you will read all day. Sorry.",
					},
				},
			}

			err = client.Send(secondMsg)
			if err != nil {
				panic(err)
			}

			Eventually(func() int {
				return len(mailServer.Deliveries)
			}).Should(Equal(2))
			delivery = mailServer.Deliveries[1]

			Expect(delivery.Sender).To(Equal("first@example.com"))
			Expect(delivery.Recipient).To(Equal("second@example.com"))
			Expect(delivery.Data).To(Equal(strings.Split(secondMsg.Data(), "\n")))
		})

		Context("when configured to use TLS", func() {
			BeforeEach(func() {
				config.SkipVerifySSL = true
				client = mail.NewClient(config, logger)
			})

			It("communicates over TLS", func() {
				msg := mail.Message{
					From:    "me@example.com",
					To:      "you@example.com",
					Subject: "Urgent! Read now!",
					Body: []mail.Part{
						{
							ContentType: "text/plain",
							Content:     "This email is the most important thing you will read all day!",
						},
					},
				}

				err := client.Send(msg)
				if err != nil {
					panic(err)
				}

				Eventually(func() int {
					return len(mailServer.Deliveries)
				}).Should(Equal(1))
				delivery := mailServer.Deliveries[0]

				Expect(delivery.Sender).To(Equal("me@example.com"))
				Expect(delivery.Recipient).To(Equal("you@example.com"))
				Expect(delivery.Data).To(Equal(strings.Split(msg.Data(), "\n")))
				Expect(delivery.UsedTLS).To(BeTrue())
			})
		})

		Context("when configured to not use TLS", func() {
			BeforeEach(func() {
				mailServer.SupportsTLS = false
				config.DisableTLS = true
				client = mail.NewClient(config, logger)
			})

			It("does not authenticate", func() {
				msg := mail.Message{
					From:    "me@example.com",
					To:      "you@example.com",
					Subject: "Urgent! Read now!",
					Body: []mail.Part{
						{
							ContentType: "text/plain",
							Content:     "This email is the most important thing you will read all day!",
						},
					},
				}

				err := client.Send(msg)
				if err != nil {
					panic(err)
				}

				Eventually(func() int {
					return len(mailServer.Deliveries)
				}).Should(Equal(1))
				delivery := mailServer.Deliveries[0]

				Expect(delivery.Sender).To(Equal("me@example.com"))
				Expect(delivery.Recipient).To(Equal("you@example.com"))
				Expect(delivery.Data).To(Equal(strings.Split(msg.Data(), "\n")))
				Expect(delivery.UsedTLS).To(BeFalse())
			})
		})

		Context("when a message fails to send", func() {
			It("quits the SMTP connection", func() {
				msg := mail.Message{
					From:    "me@example.com",
					To:      "you@example.com",
					Subject: "Urgent! Read now!",
					Body: []mail.Part{
						{
							ContentType: "text/plain",
							Content:     "This email is the most important thing you will read all day!",
						},
					},
				}
				mailServer.FailsHello = true
				err := client.Send(msg)
				Expect(err).To(HaveOccurred())

				mailServer.FailsHello = false
				err = client.Send(msg)
				Expect(err).NotTo(HaveOccurred())

				Eventually(func() int {
					return len(mailServer.Deliveries)
				}).Should(Equal(1))

				delivery := mailServer.Deliveries[0]

				Expect(delivery.Sender).To(Equal("me@example.com"))
				Expect(delivery.Recipient).To(Equal("you@example.com"))
				Expect(delivery.Data).To(Equal(strings.Split(msg.Data(), "\n")))
			})
		})
	})

	Describe("Connect", func() {
		Context("when in test mode", func() {
			It("does not connect to the smtp server", func() {
				var err error

				config.Host, config.Port, err = net.SplitHostPort("fakewebsiteoninternet.com:587")
				if err != nil {
					panic(err)
				}
				config.TestMode = true
				client = mail.NewClient(config, logger)

				err = client.Connect()
				Expect(err).To(BeNil())
			})
		})

		It("returns an error if it cannot connect within the given timeout duration", func() {
			var err error

			mailServer.ConnectWait = 5 * time.Second
			config.ConnectTimeout = 100 * time.Millisecond

			config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
			if err != nil {
				panic(err)
			}

			client = mail.NewClient(config, logger)

			err = client.Connect()
			Expect(err).ToNot(BeNil())
			Expect(err.Error()).To(ContainSubstring("server timeout"))
		})
	})

	Describe("Extension", func() {
		BeforeEach(func() {
			var err error

			mailServer.SupportsTLS = true
			config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
			if err != nil {
				panic(err)
			}

			client = mail.NewClient(config, logger)
		})

		It("returns a bool, representing presence of, and parameters for a given SMTP extension", func() {
			err := client.Connect()
			if err != nil {
				panic(err)
			}

			err = client.Hello()
			if err != nil {
				panic(err)
			}

			ok, params := client.Extension("AUTH")
			Expect(ok).To(BeTrue())
			Expect(params).To(Equal("PLAIN LOGIN"))

			ok, params = client.Extension("STARTTLS")
			Expect(ok).To(BeTrue())
			Expect(params).To(Equal(""))
		})
	})

	Describe("AuthMechanism", func() {
		Context("when configured to use PLAIN auth", func() {
			BeforeEach(func() {
				config.AuthMechanism = mail.AuthPlain
				client = mail.NewClient(config, logger)
			})

			It("creates a PlainAuth strategy", func() {
				auth := smtp.PlainAuth("", config.User, config.Pass, config.Host)
				mechanism := client.AuthMechanism()

				Expect(mechanism).To(BeAssignableToTypeOf(auth))
			})
		})

		Context("when configured to use CRAMMD5 auth", func() {
			BeforeEach(func() {
				config.AuthMechanism = mail.AuthCRAMMD5
				client = mail.NewClient(config, logger)
			})

			It("creates a CRAMMD5Auth strategy", func() {
				auth := smtp.CRAMMD5Auth(config.User, config.Secret)
				mechanism := client.AuthMechanism()

				Expect(mechanism).To(BeAssignableToTypeOf(auth))
			})
		})

		Context("when configured to use no auth", func() {
			BeforeEach(func() {
				config.AuthMechanism = mail.AuthNone
				client = mail.NewClient(config, logger)
			})

			It("creates a CRAMMD5Auth strategy", func() {
				mechanism := client.AuthMechanism()

				Expect(mechanism).To(BeNil())
			})
		})
	})

	Describe("Error", func() {
		It("logs the error and returns it", func() {
			err := errors.New("BOOM!")

			otherErr := client.Error(err)

			Expect(otherErr).To(Equal(err))

			Expect(buffer.String()).To(ContainSubstring("SMTP Error: BOOM!"))
		})

		It("quits the current connection when an error occurs", func() {
			Expect(mailServer.ConnectionState).To(Equal(StateUnknown))

			client.Connect()
			Expect(mailServer.ConnectionState).To(Equal(StateConnected))

			client.Error(errors.New("BOOM!!"))
			Expect(mailServer.ConnectionState).To(Equal(StateClosed))
		})
	})

	Describe("PrintLog", func() {
		Context("when the client is configured to log", func() {
			BeforeEach(func() {
				config.LoggingEnabled = true
				client = mail.NewClient(config, logger)
			})

			It("writes to the logger", func() {
				client.PrintLog("banana %s", "panic")

				Expect(buffer.String()).To(ContainSubstring("banana panic"))
			})
		})

		Context("when the client is not configured to log", func() {
			BeforeEach(func() {
				config.LoggingEnabled = false
				client = mail.NewClient(config, logger)
			})

			It("does not write to the logger", func() {
				client.PrintLog("banana %s", "panic")

				Expect(buffer.String()).NotTo(ContainSubstring("banana panic"))
			})
		})
	})
})
<commit_msg>Fixes date timestamp issue in mail package unit specs<commit_after>package mail_test

import (
	"bytes"
	"errors"
	"log"
	"net"
	"net/smtp"
	"strings"
	"time"

	"github.com/cloudfoundry-incubator/notifications/mail"

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

var _ = Describe("Mail", func() {
	var mailServer *SMTPServer
	var client *mail.Client
	var logger *log.Logger
	var buffer *bytes.Buffer
	var config mail.Config

	BeforeEach(func() {
		var err error

		buffer = bytes.NewBuffer([]byte{})
		logger = log.New(buffer, "", 0)
		mailServer = NewSMTPServer("user", "pass")

		config = mail.Config{
			User:          "user",
			Pass:          "pass",
			TestMode:      false,
			SkipVerifySSL: true,
			DisableTLS:    false,
		}

		config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
		if err != nil {
			panic(err)
		}

		client = mail.NewClient(config, logger)
	})

	AfterEach(func() {
		mailServer.Close()
	})

	Describe("NewClient", func() {
		It("defaults the ConnectTimeout to 15 seconds", func() {
			config.ConnectTimeout = 0

			client = mail.NewClient(config, logger)

			Expect(client.ConnectTimeout()).To(Equal(15 * time.Second))
		})
	})

	Describe("Send", func() {
		Context("when in Testmode", func() {
			var msg mail.Message

			BeforeEach(func() {
				var err error

				mailServer.SupportsTLS = true
				config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
				if err != nil {
					panic(err)
				}

				config.TestMode = true
				client = mail.NewClient(config, logger)

				msg = mail.Message{
					From:    "me@example.com",
					To:      "you@example.com",
					Subject: "Urgent! Read now!",
					Body: []mail.Part{
						{
							ContentType: "text/plain",
							Content:     "This email is the most important thing you will read all day!",
						},
					},
				}
			})

			It("does not connect to the smtp server", func() {
				err := client.Send(msg)
				if err != nil {
					panic(err)
				}

				Eventually(func() int {
					return len(mailServer.Deliveries)
				}).Should(Equal(0))
			})

			It("logs that it is in test mode", func() {
				err := client.Send(msg)
				if err != nil {
					panic(err)
				}

				line, err := buffer.ReadString('\n')
				if err != nil {
					panic(err)
				}

				Expect(line).To(Equal("TEST_MODE is enabled, emails not being sent\n"))
			})
		})
	})

	Context("when the SMTP server is properly configured", func() {
		BeforeEach(func() {
			var err error

			mailServer.SupportsTLS = true
			config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
			if err != nil {
				panic(err)
			}
			client = mail.NewClient(config, logger)
		})

		It("can send mail", func() {
			msg := mail.Message{
				From:    "me@example.com",
				To:      "you@example.com",
				Subject: "Urgent! Read now!",
				Body: []mail.Part{
					{
						ContentType: "text/plain",
						Content:     "This email is the most important thing you will read all day%40!",
					},
				},
			}

			err := client.Send(msg)
			if err != nil {
				panic(err)
			}

			Eventually(func() int {
				return len(mailServer.Deliveries)
			}).Should(Equal(1))
			delivery := mailServer.Deliveries[0]

			Expect(delivery.Sender).To(Equal("me@example.com"))
			Expect(delivery.Recipient).To(Equal("you@example.com"))
			Expect(delivery.Data).To(Equal(strings.Split(msg.Data(), "\n")))
			Expect(delivery.UsedTLS).To(BeTrue())
		})

		It("can make multiple requests", func() {
			firstMsg := mail.Message{
				From:    "me@example.com",
				To:      "you@example.com",
				Subject: "Urgent! Read now!",
				Body: []mail.Part{
					{
						ContentType: "text/plain",
						Content:     "This email is the most important thing you will read all day!",
					},
				},
			}

			err := client.Send(firstMsg)
			if err != nil {
				panic(err)
			}

			Eventually(func() int {
				return len(mailServer.Deliveries)
			}).Should(Equal(1))
			delivery := mailServer.Deliveries[0]

			Expect(delivery.Sender).To(Equal("me@example.com"))
			Expect(delivery.Recipient).To(Equal("you@example.com"))
			Expect(delivery.Data).To(Equal(strings.Split(firstMsg.Data(), "\n")))

			secondMsg := mail.Message{
				From:    "first@example.com",
				To:      "second@example.com",
				Subject: "Boring. Do not read.",
				Body: []mail.Part{
					{
						ContentType: "text/plain",
						Content:     "This email is the least important thing you will read all day. Sorry.",
					},
				},
			}

			err = client.Send(secondMsg)
			if err != nil {
				panic(err)
			}

			Eventually(func() int {
				return len(mailServer.Deliveries)
			}).Should(Equal(2))
			delivery = mailServer.Deliveries[1]

			Expect(delivery.Sender).To(Equal("first@example.com"))
			Expect(delivery.Recipient).To(Equal("second@example.com"))
			Expect(delivery.Data).To(Equal(strings.Split(secondMsg.Data(), "\n")))
		})

		Context("when configured to use TLS", func() {
			BeforeEach(func() {
				config.SkipVerifySSL = true
				client = mail.NewClient(config, logger)
			})

			It("communicates over TLS", func() {
				msg := mail.Message{
					From:    "me@example.com",
					To:      "you@example.com",
					Subject: "Urgent! Read now!",
					Body: []mail.Part{
						{
							ContentType: "text/plain",
							Content:     "This email is the most important thing you will read all day!",
						},
					},
				}

				err := client.Send(msg)
				if err != nil {
					panic(err)
				}

				Eventually(func() int {
					return len(mailServer.Deliveries)
				}).Should(Equal(1))

				delivery := mailServer.Deliveries[0]
				Expect(delivery.UsedTLS).To(BeTrue())
			})
		})

		Context("when configured to not use TLS", func() {
			BeforeEach(func() {
				mailServer.SupportsTLS = false
				config.DisableTLS = true
				client = mail.NewClient(config, logger)
			})

			It("does not authenticate", func() {
				msg := mail.Message{
					From:    "me@example.com",
					To:      "you@example.com",
					Subject: "Urgent! Read now!",
					Body: []mail.Part{
						{
							ContentType: "text/plain",
							Content:     "This email is the most important thing you will read all day!",
						},
					},
				}

				err := client.Send(msg)
				if err != nil {
					panic(err)
				}

				Eventually(func() int {
					return len(mailServer.Deliveries)
				}).Should(Equal(1))
				delivery := mailServer.Deliveries[0]
				Expect(delivery.UsedTLS).To(BeFalse())
			})
		})

		Context("when a message fails to send", func() {
			It("quits the SMTP connection", func() {
				msg := mail.Message{
					From:    "me@example.com",
					To:      "you@example.com",
					Subject: "Urgent! Read now!",
					Body: []mail.Part{
						{
							ContentType: "text/plain",
							Content:     "This email is the most important thing you will read all day!",
						},
					},
				}
				mailServer.FailsHello = true
				err := client.Send(msg)
				Expect(err).To(HaveOccurred())

				Consistently(func() int {
					return len(mailServer.Deliveries)
				}).Should(Equal(0))

				mailServer.FailsHello = false
				err = client.Send(msg)
				Expect(err).NotTo(HaveOccurred())

				Eventually(func() int {
					return len(mailServer.Deliveries)
				}).Should(Equal(1))
			})
		})
	})

	Describe("Connect", func() {
		Context("when in test mode", func() {
			It("does not connect to the smtp server", func() {
				var err error

				config.Host, config.Port, err = net.SplitHostPort("fakewebsiteoninternet.com:587")
				if err != nil {
					panic(err)
				}
				config.TestMode = true
				client = mail.NewClient(config, logger)

				err = client.Connect()
				Expect(err).To(BeNil())
			})
		})

		It("returns an error if it cannot connect within the given timeout duration", func() {
			var err error

			mailServer.ConnectWait = 5 * time.Second
			config.ConnectTimeout = 100 * time.Millisecond

			config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
			if err != nil {
				panic(err)
			}

			client = mail.NewClient(config, logger)

			err = client.Connect()
			Expect(err).ToNot(BeNil())
			Expect(err.Error()).To(ContainSubstring("server timeout"))
		})
	})

	Describe("Extension", func() {
		BeforeEach(func() {
			var err error

			mailServer.SupportsTLS = true
			config.Host, config.Port, err = net.SplitHostPort(mailServer.URL.String())
			if err != nil {
				panic(err)
			}

			client = mail.NewClient(config, logger)
		})

		It("returns a bool, representing presence of, and parameters for a given SMTP extension", func() {
			err := client.Connect()
			if err != nil {
				panic(err)
			}

			err = client.Hello()
			if err != nil {
				panic(err)
			}

			ok, params := client.Extension("AUTH")
			Expect(ok).To(BeTrue())
			Expect(params).To(Equal("PLAIN LOGIN"))

			ok, params = client.Extension("STARTTLS")
			Expect(ok).To(BeTrue())
			Expect(params).To(Equal(""))
		})
	})

	Describe("AuthMechanism", func() {
		Context("when configured to use PLAIN auth", func() {
			BeforeEach(func() {
				config.AuthMechanism = mail.AuthPlain
				client = mail.NewClient(config, logger)
			})

			It("creates a PlainAuth strategy", func() {
				auth := smtp.PlainAuth("", config.User, config.Pass, config.Host)
				mechanism := client.AuthMechanism()

				Expect(mechanism).To(BeAssignableToTypeOf(auth))
			})
		})

		Context("when configured to use CRAMMD5 auth", func() {
			BeforeEach(func() {
				config.AuthMechanism = mail.AuthCRAMMD5
				client = mail.NewClient(config, logger)
			})

			It("creates a CRAMMD5Auth strategy", func() {
				auth := smtp.CRAMMD5Auth(config.User, config.Secret)
				mechanism := client.AuthMechanism()

				Expect(mechanism).To(BeAssignableToTypeOf(auth))
			})
		})

		Context("when configured to use no auth", func() {
			BeforeEach(func() {
				config.AuthMechanism = mail.AuthNone
				client = mail.NewClient(config, logger)
			})

			It("creates a CRAMMD5Auth strategy", func() {
				mechanism := client.AuthMechanism()

				Expect(mechanism).To(BeNil())
			})
		})
	})

	Describe("Error", func() {
		It("logs the error and returns it", func() {
			err := errors.New("BOOM!")

			otherErr := client.Error(err)

			Expect(otherErr).To(Equal(err))

			Expect(buffer.String()).To(ContainSubstring("SMTP Error: BOOM!"))
		})

		It("quits the current connection when an error occurs", func() {
			Expect(mailServer.ConnectionState).To(Equal(StateUnknown))

			client.Connect()
			Expect(mailServer.ConnectionState).To(Equal(StateConnected))

			client.Error(errors.New("BOOM!!"))
			Expect(mailServer.ConnectionState).To(Equal(StateClosed))
		})
	})

	Describe("PrintLog", func() {
		Context("when the client is configured to log", func() {
			BeforeEach(func() {
				config.LoggingEnabled = true
				client = mail.NewClient(config, logger)
			})

			It("writes to the logger", func() {
				client.PrintLog("banana %s", "panic")

				Expect(buffer.String()).To(ContainSubstring("banana panic"))
			})
		})

		Context("when the client is not configured to log", func() {
			BeforeEach(func() {
				config.LoggingEnabled = false
				client = mail.NewClient(config, logger)
			})

			It("does not write to the logger", func() {
				client.PrintLog("banana %s", "panic")

				Expect(buffer.String()).NotTo(ContainSubstring("banana panic"))
			})
		})
	})
})
<|endoftext|>


<commit_before>package smux

import (
	crand "crypto/rand"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"sync"
	"testing"
	"time"
)

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	ln, err := net.Listen("tcp", "127.0.0.1:19999")
	if err != nil {
		// handle error
		panic(err)
	}
	go func() {
		for {
			conn, err := ln.Accept()
			if err != nil {
				// handle error
			}
			go handleConnection(conn)
		}
	}()
}

func handleConnection(conn net.Conn) {
	session, _ := Server(conn, nil)
	for {
		if stream, err := session.AcceptStream(); err == nil {
			go func(s io.ReadWriteCloser) {
				buf := make([]byte, 65536)
				for {
					n, err := s.Read(buf)
					if err != nil {
						return
					}
					s.Write(buf[:n])
				}
			}(stream)
		} else {
			log.Println(err)
			return
		}
	}
}

func TestEcho(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()
	const N = 100
	buf := make([]byte, 10)
	var sent string
	var received string
	for i := 0; i < N; i++ {
		msg := fmt.Sprintf("hello%v", i)
		stream.Write([]byte(msg))
		sent += msg
		if n, err := stream.Read(buf); err != nil {
			t.Fatal(err)
		} else {
			received += string(buf[:n])
		}
	}
	if sent != received {
		t.Fatal("data mimatch")
	}
	session.Close()
}

func TestSpeed(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()

	start := time.Now()
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		buf := make([]byte, 1024*1024)
		nrecv := 0
		for {
			n, err := stream.Read(buf)
			if err != nil {
				t.Fatal(err)
				break
			} else {
				nrecv += n
				if nrecv == 4096*4096 {
					break
				}
			}
		}
		println("total recv:", nrecv)
		stream.Close()
		fmt.Println("time for 16MB rtt", time.Now().Sub(start))
		wg.Done()
	}()
	msg := make([]byte, 8192)
	for i := 0; i < 2048; i++ {
		stream.Write(msg)
	}
	wg.Wait()
	session.Close()
}

func TestParallel(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)

	par := 1000
	messages := 100
	var wg sync.WaitGroup
	wg.Add(par)
	for i := 0; i < par; i++ {
		stream, _ := session.OpenStream()
		go func(s *Stream) {
			buf := make([]byte, 20)
			for j := 0; j < messages; j++ {
				msg := fmt.Sprintf("hello%v", j)
				s.Write([]byte(msg))
				if _, err := s.Read(buf); err != nil {
					break
				}
			}
			s.Close()
			wg.Done()
		}(stream)
	}
	t.Log("created", session.NumStreams(), "streams")
	wg.Wait()
	session.Close()
}

func TestCloseThenOpen(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	session.Close()
	if _, err := session.OpenStream(); err == nil {
		t.Fatal("opened after close")
	}
}

func TestTinyReadBuffer(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()
	const N = 100
	tinybuf := make([]byte, 6)
	var sent string
	var received string
	for i := 0; i < N; i++ {
		msg := fmt.Sprintf("hello%v", i)
		sent += msg
		nsent, err := stream.Write([]byte(msg))
		if err != nil {
			t.Fatal("cannot write")
		}
		nrecv := 0
		for nrecv < nsent {
			if n, err := stream.Read(tinybuf); err == nil {
				nrecv += n
				received += string(tinybuf[:n])
			} else {
				t.Fatal("cannot read with tiny buffer")
			}
		}
	}

	if sent != received {
		t.Fatal("data mimatch")
	}
	session.Close()
}

func TestIsClose(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	session.Close()
	if session.IsClosed() != true {
		t.Fatal("still open after close")
	}
}

func TestKeepAliveTimeout(t *testing.T) {
	ln, err := net.Listen("tcp", "127.0.0.1:29999")
	if err != nil {
		// handle error
		panic(err)
	}
	go func() {
		ln.Accept()
	}()

	cli, err := net.Dial("tcp", "127.0.0.1:29999")
	if err != nil {
		t.Fatal(err)
	}

	config := DefaultConfig()
	config.KeepAliveInterval = time.Second
	config.KeepAliveTimeout = 2 * time.Second
	session, _ := Client(cli, config)
	<-time.After(3 * time.Second)
	if session.IsClosed() != true {
		t.Fatal("keepalive-timeout failed")
	}
}

func TestServerEcho(t *testing.T) {
	ln, err := net.Listen("tcp", "127.0.0.1:39999")
	if err != nil {
		// handle error
		panic(err)
	}
	go func() {
		if conn, err := ln.Accept(); err == nil {
			session, _ := Server(conn, nil)
			if stream, err := session.OpenStream(); err == nil {
				const N = 100
				buf := make([]byte, 10)
				for i := 0; i < N; i++ {
					msg := fmt.Sprintf("hello%v", i)
					stream.Write([]byte(msg))
					if n, err := stream.Read(buf); err != nil {
						t.Fatal(err)
					} else if string(buf[:n]) != msg {
						t.Fatal(err)
					}
				}
				stream.Close()
			} else {
				t.Fatal(err)
			}
		} else {
			t.Fatal(err)
		}
	}()

	cli, err := net.Dial("tcp", "127.0.0.1:39999")
	if err != nil {
		t.Fatal(err)
	}
	if session, err := Client(cli, nil); err == nil {
		if stream, err := session.AcceptStream(); err == nil {
			buf := make([]byte, 65536)
			for {
				n, err := stream.Read(buf)
				if err != nil {
					break
				}
				stream.Write(buf[:n])
			}
		} else {
			t.Fatal(err)
		}
	} else {
		t.Fatal(err)
	}
}

func TestSendWithoutRecv(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()
	const N = 100
	for i := 0; i < N; i++ {
		msg := fmt.Sprintf("hello%v", i)
		stream.Write([]byte(msg))
	}
	buf := make([]byte, 1)
	if _, err := stream.Read(buf); err != nil {
		t.Fatal(err)
	}
	stream.Close()
}

func TestWriteAfterClose(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()
	stream.Close()
	if _, err := stream.Write([]byte("write after close")); err == nil {
		t.Fatal("write after close failed")
	}
}

func TestRandomFrame(t *testing.T) {
	// pure random
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	for i := 0; i < 100; i++ {
		rnd := make([]byte, rand.Uint32()%1024)
		io.ReadFull(crand.Reader, rnd)
		session.conn.Write(rnd)
	}
	cli.Close()

	// double syn
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ = Client(cli, nil)
	for i := 0; i < 100; i++ {
		f := newFrame(cmdSYN, 1000)
		session.writeFrame(f)
	}
	cli.Close()

	// random cmds
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	allcmds := []byte{cmdSYN, cmdRST, cmdPSH, cmdNOP, cmdTerminate}
	session, _ = Client(cli, nil)
	for i := 0; i < 100; i++ {
		f := newFrame(allcmds[rand.Int()%len(allcmds)], rand.Uint32())
		session.writeFrame(f)
	}
	cli.Close()

	// random cmds & sids
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ = Client(cli, nil)
	for i := 0; i < 100; i++ {
		f := newFrame(byte(rand.Uint32()), rand.Uint32())
		session.writeFrame(f)
	}
	cli.Close()

	// random version
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ = Client(cli, nil)
	for i := 0; i < 100; i++ {
		f := newFrame(byte(rand.Uint32()), rand.Uint32())
		f.ver = byte(rand.Uint32())
		session.writeFrame(f)
	}
	cli.Close()

	// incorrect size
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ = Client(cli, nil)
	f := newFrame(byte(rand.Uint32()), rand.Uint32())
	f.ver = byte(rand.Uint32())
	bts, _ := f.MarshalBinary()
	rnd := make([]byte, rand.Uint32()%1024)
	io.ReadFull(crand.Reader, rnd)
	bts = append(bts, rnd...)
	binary.LittleEndian.PutUint16(bts[2:], uint16(len(rnd)+1)) /// incorrect size
	session.conn.Write(bts)
	cli.Close()
}

func BenchmarkAcceptClose(b *testing.B) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		b.Fatal(err)
	}
	session, _ := Client(cli, nil)
	for i := 0; i < b.N; i++ {
		if stream, err := session.OpenStream(); err == nil {
			stream.Close()
		} else {
			b.Fatal(err)
		}
	}
}
<commit_msg>add test<commit_after>package smux

import (
	crand "crypto/rand"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"sync"
	"testing"
	"time"
)

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	ln, err := net.Listen("tcp", "127.0.0.1:19999")
	if err != nil {
		// handle error
		panic(err)
	}
	go func() {
		for {
			conn, err := ln.Accept()
			if err != nil {
				// handle error
			}
			go handleConnection(conn)
		}
	}()
}

func handleConnection(conn net.Conn) {
	session, _ := Server(conn, nil)
	for {
		if stream, err := session.AcceptStream(); err == nil {
			go func(s io.ReadWriteCloser) {
				buf := make([]byte, 65536)
				for {
					n, err := s.Read(buf)
					if err != nil {
						return
					}
					s.Write(buf[:n])
				}
			}(stream)
		} else {
			log.Println(err)
			return
		}
	}
}

func TestEcho(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()
	const N = 100
	buf := make([]byte, 10)
	var sent string
	var received string
	for i := 0; i < N; i++ {
		msg := fmt.Sprintf("hello%v", i)
		stream.Write([]byte(msg))
		sent += msg
		if n, err := stream.Read(buf); err != nil {
			t.Fatal(err)
		} else {
			received += string(buf[:n])
		}
	}
	if sent != received {
		t.Fatal("data mimatch")
	}
	session.Close()
}

func TestSpeed(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()

	start := time.Now()
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		buf := make([]byte, 1024*1024)
		nrecv := 0
		for {
			n, err := stream.Read(buf)
			if err != nil {
				t.Fatal(err)
				break
			} else {
				nrecv += n
				if nrecv == 4096*4096 {
					break
				}
			}
		}
		println("total recv:", nrecv)
		stream.Close()
		fmt.Println("time for 16MB rtt", time.Now().Sub(start))
		wg.Done()
	}()
	msg := make([]byte, 8192)
	for i := 0; i < 2048; i++ {
		stream.Write(msg)
	}
	wg.Wait()
	session.Close()
}

func TestParallel(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)

	par := 1000
	messages := 100
	var wg sync.WaitGroup
	wg.Add(par)
	for i := 0; i < par; i++ {
		stream, _ := session.OpenStream()
		go func(s *Stream) {
			buf := make([]byte, 20)
			for j := 0; j < messages; j++ {
				msg := fmt.Sprintf("hello%v", j)
				s.Write([]byte(msg))
				if _, err := s.Read(buf); err != nil {
					break
				}
			}
			s.Close()
			wg.Done()
		}(stream)
	}
	t.Log("created", session.NumStreams(), "streams")
	wg.Wait()
	session.Close()
}

func TestCloseThenOpen(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	session.Close()
	if _, err := session.OpenStream(); err == nil {
		t.Fatal("opened after close")
	}
}

func TestTinyReadBuffer(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()
	const N = 100
	tinybuf := make([]byte, 6)
	var sent string
	var received string
	for i := 0; i < N; i++ {
		msg := fmt.Sprintf("hello%v", i)
		sent += msg
		nsent, err := stream.Write([]byte(msg))
		if err != nil {
			t.Fatal("cannot write")
		}
		nrecv := 0
		for nrecv < nsent {
			if n, err := stream.Read(tinybuf); err == nil {
				nrecv += n
				received += string(tinybuf[:n])
			} else {
				t.Fatal("cannot read with tiny buffer")
			}
		}
	}

	if sent != received {
		t.Fatal("data mimatch")
	}
	session.Close()
}

func TestIsClose(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	session.Close()
	if session.IsClosed() != true {
		t.Fatal("still open after close")
	}
}

func TestKeepAliveTimeout(t *testing.T) {
	ln, err := net.Listen("tcp", "127.0.0.1:29999")
	if err != nil {
		// handle error
		panic(err)
	}
	go func() {
		ln.Accept()
	}()

	cli, err := net.Dial("tcp", "127.0.0.1:29999")
	if err != nil {
		t.Fatal(err)
	}

	config := DefaultConfig()
	config.KeepAliveInterval = time.Second
	config.KeepAliveTimeout = 2 * time.Second
	session, _ := Client(cli, config)
	<-time.After(3 * time.Second)
	if session.IsClosed() != true {
		t.Fatal("keepalive-timeout failed")
	}
}

func TestServerEcho(t *testing.T) {
	ln, err := net.Listen("tcp", "127.0.0.1:39999")
	if err != nil {
		// handle error
		panic(err)
	}
	go func() {
		if conn, err := ln.Accept(); err == nil {
			session, _ := Server(conn, nil)
			if stream, err := session.OpenStream(); err == nil {
				const N = 100
				buf := make([]byte, 10)
				for i := 0; i < N; i++ {
					msg := fmt.Sprintf("hello%v", i)
					stream.Write([]byte(msg))
					if n, err := stream.Read(buf); err != nil {
						t.Fatal(err)
					} else if string(buf[:n]) != msg {
						t.Fatal(err)
					}
				}
				stream.Close()
			} else {
				t.Fatal(err)
			}
		} else {
			t.Fatal(err)
		}
	}()

	cli, err := net.Dial("tcp", "127.0.0.1:39999")
	if err != nil {
		t.Fatal(err)
	}
	if session, err := Client(cli, nil); err == nil {
		if stream, err := session.AcceptStream(); err == nil {
			buf := make([]byte, 65536)
			for {
				n, err := stream.Read(buf)
				if err != nil {
					break
				}
				stream.Write(buf[:n])
			}
		} else {
			t.Fatal(err)
		}
	} else {
		t.Fatal(err)
	}
}

func TestSendWithoutRecv(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()
	const N = 100
	for i := 0; i < N; i++ {
		msg := fmt.Sprintf("hello%v", i)
		stream.Write([]byte(msg))
	}
	buf := make([]byte, 1)
	if _, err := stream.Read(buf); err != nil {
		t.Fatal(err)
	}
	stream.Close()
}

func TestWriteAfterClose(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	stream, _ := session.OpenStream()
	stream.Close()
	if _, err := stream.Write([]byte("write after close")); err == nil {
		t.Fatal("write after close failed")
	}
}

func TestNumStreamAfterClose(t *testing.T) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	if _, err := session.OpenStream(); err == nil {
		if session.NumStreams() != 1 {
			t.Fatal("wrong number of streams after opened")
		}
		session.Close()
		if session.NumStreams() != 0 {
			t.Fatal("wrong number of streams after session closed")
		}
	} else {
		t.Fatal(err)
	}
	cli.Close()
}

func TestRandomFrame(t *testing.T) {
	// pure random
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ := Client(cli, nil)
	for i := 0; i < 100; i++ {
		rnd := make([]byte, rand.Uint32()%1024)
		io.ReadFull(crand.Reader, rnd)
		session.conn.Write(rnd)
	}
	cli.Close()

	// double syn
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ = Client(cli, nil)
	for i := 0; i < 100; i++ {
		f := newFrame(cmdSYN, 1000)
		session.writeFrame(f)
	}
	cli.Close()

	// random cmds
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	allcmds := []byte{cmdSYN, cmdRST, cmdPSH, cmdNOP, cmdTerminate}
	session, _ = Client(cli, nil)
	for i := 0; i < 100; i++ {
		f := newFrame(allcmds[rand.Int()%len(allcmds)], rand.Uint32())
		session.writeFrame(f)
	}
	cli.Close()

	// random cmds & sids
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ = Client(cli, nil)
	for i := 0; i < 100; i++ {
		f := newFrame(byte(rand.Uint32()), rand.Uint32())
		session.writeFrame(f)
	}
	cli.Close()

	// random version
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ = Client(cli, nil)
	for i := 0; i < 100; i++ {
		f := newFrame(byte(rand.Uint32()), rand.Uint32())
		f.ver = byte(rand.Uint32())
		session.writeFrame(f)
	}
	cli.Close()

	// incorrect size
	cli, err = net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		t.Fatal(err)
	}
	session, _ = Client(cli, nil)
	f := newFrame(byte(rand.Uint32()), rand.Uint32())
	f.ver = byte(rand.Uint32())
	bts, _ := f.MarshalBinary()
	rnd := make([]byte, rand.Uint32()%1024)
	io.ReadFull(crand.Reader, rnd)
	bts = append(bts, rnd...)
	binary.LittleEndian.PutUint16(bts[2:], uint16(len(rnd)+1)) /// incorrect size
	session.conn.Write(bts)
	cli.Close()
}

func BenchmarkAcceptClose(b *testing.B) {
	cli, err := net.Dial("tcp", "127.0.0.1:19999")
	if err != nil {
		b.Fatal(err)
	}
	session, _ := Client(cli, nil)
	for i := 0; i < b.N; i++ {
		if stream, err := session.OpenStream(); err == nil {
			stream.Close()
		} else {
			b.Fatal(err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"koding/db/mongodb/modelhelper"
	"socialapi/config"
	"socialapi/workers/migrator/controller"
	"socialapi/workers/realtime/models"

	"github.com/koding/runner"
)

var (
	Name         = "Migrator"
	flagSchedule = flag.Bool("s", false, "Schedule worker")
	flagPubNub   = flag.Bool("p", false, "Grant public access to channels")
)

func main() {
	r := runner.New(Name)
	if err := r.Init(); err != nil {
		fmt.Println(err)
		return
	}
	defer r.Close()

	appConfig := config.MustRead(r.Conf.Path)
	modelhelper.Initialize(appConfig.Mongo)

	pubnub := models.NewPubNub(appConfig.GateKeeper.Pubnub, r.Log)
	defer pubnub.Close()

	handler, err := controller.New(r.Log, pubnub)
	if err != nil {
		panic(err)
	}

	if *flagSchedule {
		r.ShutdownHandler = handler.Shutdown
		if err := handler.Schedule(); err != nil {
			panic(err)
		}
		r.Wait()

		return
	}

	if *flagPubNub {
		handler.GrantPublicAccess()

		return
	}

	handler.Start()
}
<commit_msg>migrator: add flag for integration creator<commit_after>package main

import (
	"flag"
	"fmt"
	"koding/db/mongodb/modelhelper"
	"socialapi/config"
	"socialapi/workers/migrator/controller"
	"socialapi/workers/realtime/models"

	"github.com/koding/runner"
)

var (
	Name            = "Migrator"
	flagSchedule    = flag.Bool("s", false, "Schedule worker")
	flagPubNub      = flag.Bool("p", false, "Grant public access to channels")
	flagIntegration = flag.Bool("i", false, "Create integrations")
)

func main() {
	r := runner.New(Name)
	if err := r.Init(); err != nil {
		fmt.Println(err)
		return
	}
	defer r.Close()

	appConfig := config.MustRead(r.Conf.Path)
	modelhelper.Initialize(appConfig.Mongo)

	pubnub := models.NewPubNub(appConfig.GateKeeper.Pubnub, r.Log)
	defer pubnub.Close()

	handler, err := controller.New(r.Log, pubnub)
	if err != nil {
		panic(err)
	}

	if *flagSchedule {
		r.ShutdownHandler = handler.Shutdown
		if err := handler.Schedule(); err != nil {
			panic(err)
		}
		r.Wait()

		return
	}

	if *flagPubNub {
		handler.GrantPublicAccess()

		return
	}

	if *flagIntegration {
		handler.CreateIntegrations()

		return
	}

	handler.Start()
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"net/http"
	"strings"
	"testing"

	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"

	"google.golang.org/api/googleapi"
)

// Unit Tests

func TestDatabaseNameForApi(t *testing.T) {
	id := spannerDatabaseId{
		Project:  "project123",
		Instance: "instance456",
		Database: "db789",
	}
	actual := id.databaseUri()
	expected := "projects/project123/instances/instance456/databases/db789"
	expectEquals(t, expected, actual)
}

func TestImportSpannerDatabaseId_InstanceDB(t *testing.T) {
	id, e := importSpannerDatabaseId("instance456/database789")
	if e != nil {
		t.Errorf("Error should have been nil")
	}
	expectEquals(t, "", id.Project)
	expectEquals(t, "instance456", id.Instance)
	expectEquals(t, "database789", id.Database)
}

func TestImportSpannerDatabaseId_ProjectInstanceDB(t *testing.T) {
	id, e := importSpannerDatabaseId("project123/instance456/database789")
	if e != nil {
		t.Errorf("Error should have been nil")
	}
	expectEquals(t, "project123", id.Project)
	expectEquals(t, "instance456", id.Instance)
	expectEquals(t, "database789", id.Database)
}

func TestImportSpannerDatabaseId_projectId(t *testing.T) {
	shouldPass := []string{
		"project-id/instance/database",
		"123123/instance/123",
		"hashicorptest.net:project-123/instance/123",
		"123/456/789",
	}

	shouldFail := []string{
		"project-id#/instance/database",
		"project-id/instance#/database",
		"project-id/instance/database#",
		"hashicorptest.net:project-123:invalid:project/instance/123",
		"hashicorptest.net:/instance/123",
	}

	for _, element := range shouldPass {
		_, e := importSpannerDatabaseId(element)
		if e != nil {
			t.Error("importSpannerDatabaseId should pass on '" + element + "' but doesn't")
		}
	}

	for _, element := range shouldFail {
		_, e := importSpannerDatabaseId(element)
		if e == nil {
			t.Error("importSpannerDatabaseId should fail on '" + element + "' but doesn't")
		}
	}
}

func TestImportSpannerDatabaseId_invalidLeadingSlash(t *testing.T) {
	id, e := importSpannerDatabaseId("/instance456/database789")
	expectInvalidSpannerDbImportId(t, id, e)
}

func TestImportSpannerDatabaseId_invalidTrailingSlash(t *testing.T) {
	id, e := importSpannerDatabaseId("instance456/database789/")
	expectInvalidSpannerDbImportId(t, id, e)
}

func TestImportSpannerDatabaseId_invalidSingleSlash(t *testing.T) {
	id, e := importSpannerDatabaseId("/")
	expectInvalidSpannerDbImportId(t, id, e)
}

func TestImportSpannerDatabaseId_invalidMultiSlash(t *testing.T) {
	id, e := importSpannerDatabaseId("project123/instance456/db789/next")
	expectInvalidSpannerDbImportId(t, id, e)
}

func expectInvalidSpannerDbImportId(t *testing.T, id *spannerDatabaseId, e error) {
	if id != nil {
		t.Errorf("Expected spannerDatabaseId to be nil")
		return
	}
	if e == nil {
		t.Errorf("Expected an Error but did not get one")
		return
	}
	if !strings.HasPrefix(e.Error(), "Invalid spanner database specifier") {
		t.Errorf("Expecting Error starting with 'Invalid spanner database specifier'")
	}
}

// Acceptance Tests

func TestAccSpannerDatabase_basic(t *testing.T) {
	t.Parallel()

	rnd := acctest.RandString(10)
	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckSpannerDatabaseDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccSpannerDatabase_basic(rnd),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttrSet("google_spanner_database.basic", "state"),
				),
			},
			{
				ResourceName:      "google_spanner_database.basic",
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccSpannerDatabase_basicWithInitialDDL(t *testing.T) {
	t.Parallel()

	rnd := acctest.RandString(10)
	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckSpannerDatabaseDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccSpannerDatabase_basicWithInitialDDL(rnd),
			},
			{
				ResourceName:      "google_spanner_database.basic",
				ImportState:       true,
				ImportStateVerify: true,
				// DDL statements get issued at the time the create/update
				// occurs, which means storing them in state isn't really
				// necessary.
				ImportStateVerifyIgnore: []string{"ddl"},
			},
		},
	})
}

func testAccCheckSpannerDatabaseDestroy(s *terraform.State) error {
	config := testAccProvider.Meta().(*Config)

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "google_spanner_database" {
			continue
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("Unable to verify delete of spanner database, ID is empty")
		}

		project, err := getTestProject(rs.Primary, config)
		if err != nil {
			return err
		}

		id := spannerDatabaseId{
			Project:  project,
			Instance: rs.Primary.Attributes["instance"],
			Database: rs.Primary.Attributes["name"],
		}
		_, err = config.clientSpanner.Projects.Instances.Databases.Get(
			id.databaseUri()).Do()

		if err == nil {
			return fmt.Errorf("Spanner database still exists")
		}

		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound {
			return nil
		}
		return errwrap.Wrapf("Error verifying spanner database deleted: {{err}}", err)
	}

	return nil
}

func testAccSpannerDatabase_basic(rnd string) string {
	return fmt.Sprintf(`
resource "google_spanner_instance" "basic" {
  name          = "my-instance-%s"
  config        = "regional-us-central1"
  display_name  = "my-displayname-%s"
  num_nodes     = 1
}

resource "google_spanner_database" "basic" {
  instance      = "${google_spanner_instance.basic.name}"
  name          = "my-db-%s"
}
`, rnd, rnd, rnd)
}

func testAccSpannerDatabase_basicWithInitialDDL(rnd string) string {
	return fmt.Sprintf(`
resource "google_spanner_instance" "basic" {
  name          = "my-instance-%s"
  config        = "regional-us-central1"
  display_name  = "my-displayname-%s"
  num_nodes     = 1
}

resource "google_spanner_database" "basic" {
  instance      = "${google_spanner_instance.basic.name}"
  name          = "my-db-%s"
  ddl           =  [
     "CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)",
     "CREATE TABLE t2 (t2 INT64 NOT NULL,) PRIMARY KEY(t2)" ]
}
`, rnd, rnd, rnd)
}
<commit_msg>Magic Modules changes.<commit_after>package google

import (
	"fmt"
	"net/http"
	"testing"

	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"

	"google.golang.org/api/googleapi"
)

func TestAccSpannerDatabase_basic(t *testing.T) {
	t.Parallel()

	project := getTestProjectFromEnv()
	rnd := acctest.RandString(10)
	instanceName := fmt.Sprintf("my-instance-%s", rnd)
	databaseName := fmt.Sprintf("mydb_%s", rnd)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckSpannerDatabaseDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccSpannerDatabase_basic(instanceName, databaseName),
				Check: resource.ComposeTestCheckFunc(
					resource.TestCheckResourceAttrSet("google_spanner_database.basic", "state"),
				),
			},
			{
				// Test import with default Terraform ID
				ResourceName:      "google_spanner_database.basic",
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				ResourceName:      "google_spanner_database.basic",
				ImportStateId:     fmt.Sprintf("projects/%s/instances/%s/databases/%s", project, instanceName, databaseName),
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				ResourceName:      "google_spanner_database.basic",
				ImportStateId:     fmt.Sprintf("instances/%s/databases/%s", instanceName, databaseName),
				ImportState:       true,
				ImportStateVerify: true,
			},
			{
				ResourceName:      "google_spanner_database.basic",
				ImportStateId:     fmt.Sprintf("%s/%s", instanceName, databaseName),
				ImportState:       true,
				ImportStateVerify: true,
			},
		},
	})
}

func TestAccSpannerDatabase_basicWithInitialDDL(t *testing.T) {
	t.Parallel()

	rnd := acctest.RandString(10)
	instanceName := fmt.Sprintf("my-instance-%s", rnd)
	databaseName := fmt.Sprintf("mydb-%s", rnd)
	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckSpannerDatabaseDestroy,
		Steps: []resource.TestStep{
			{
				Config: testAccSpannerDatabase_basicWithInitialDDL(instanceName, databaseName),
			},
			{
				ResourceName:      "google_spanner_database.basic",
				ImportState:       true,
				ImportStateVerify: true,
				// DDL statements get issued at the time the create/update
				// occurs, which means storing them in state isn't really
				// necessary.
				ImportStateVerifyIgnore: []string{"ddl"},
			},
		},
	})
}

func testAccCheckSpannerDatabaseDestroy(s *terraform.State) error {
	config := testAccProvider.Meta().(*Config)

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "google_spanner_database" {
			continue
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("Unable to verify delete of spanner database, ID is empty")
		}

		project, err := getTestProject(rs.Primary, config)
		if err != nil {
			return err
		}

		id := spannerDatabaseId{
			Project:  project,
			Instance: rs.Primary.Attributes["instance"],
			Database: rs.Primary.Attributes["name"],
		}
		_, err = config.clientSpanner.Projects.Instances.Databases.Get(
			id.databaseUri()).Do()

		if err == nil {
			return fmt.Errorf("Spanner database still exists")
		}

		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound {
			return nil
		}
		return errwrap.Wrapf("Error verifying spanner database deleted: {{err}}", err)
	}

	return nil
}

func testAccSpannerDatabase_basic(instanceName, databaseName string) string {
	return fmt.Sprintf(`
resource "google_spanner_instance" "basic" {
  name          = "%s"
  config        = "regional-us-central1"
  display_name  = "display-%s"
  num_nodes     = 1
}

resource "google_spanner_database" "basic" {
  instance      = "${google_spanner_instance.basic.name}"
  name          = "%s"
}
`, instanceName, instanceName, databaseName)
}

func testAccSpannerDatabase_basicWithInitialDDL(instanceName, databaseName string) string {
	return fmt.Sprintf(`
resource "google_spanner_instance" "basic" {
  name          = "%s"
  config        = "regional-us-central1"
  display_name  = "display-%s"
  num_nodes     = 1
}

resource "google_spanner_database" "basic" {
  instance      = "${google_spanner_instance.basic.name}"
  name          = "%s"
  ddl           =  [
     "CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)",
     "CREATE TABLE t2 (t2 INT64 NOT NULL,) PRIMARY KEY(t2)" ]
}
`, instanceName, instanceName, databaseName)
}

// Unit Tests for type spannerDatabaseId
func TestDatabaseNameForApi(t *testing.T) {
	id := spannerDatabaseId{
		Project:  "project123",
		Instance: "instance456",
		Database: "db789",
	}
	actual := id.databaseUri()
	expected := "projects/project123/instances/instance456/databases/db789"
	expectEquals(t, expected, actual)
}
<|endoftext|>


<commit_before>package pixel

const vertexShader = "\n" + `#version 460 core

//------------------------------------------------------------------------------

const uint cmdPicture    = 1;
const uint cmdPictureExt = 2;
const uint cmdText       = 3;
const uint cmdPoint      = 4;
const uint cmdLines      = 5;
const uint cmdTriangles  = 6;

const vec2 corners[4] = vec2[4](
	vec2(0, 0),
	vec2(1, 0),
	vec2(0, 1),
	vec2(1, 1)
);

//------------------------------------------------------------------------------

layout(std140, binding = 0) uniform ScreenUBO {
	vec2 PixelSize;
};

//------------------------------------------------------------------------------

layout(binding = 0) uniform isamplerBuffer parameters;
layout(binding = 1) uniform isamplerBuffer pictureMap;
layout(binding = 3) uniform isamplerBuffer glyphMap;

//------------------------------------------------------------------------------

out gl_PerVertex {
	vec4 gl_Position;
};

out PerVertex {
	layout(location=0) flat uint Command;
	layout(location=1) flat uint Bin;
	layout(location=2) vec2 UV;
	layout(location=3) flat uint ColorIndex;
	layout(location=4) flat vec2 Orig;
	layout(location=5) flat float Slope;
	layout(location=6) flat bool Steep;
};

//------------------------------------------------------------------------------

float floatZ(int z) {
	return float(z)/float(0x7FFF);
}

//------------------------------------------------------------------------------

void main(void)
{
	Command = gl_BaseInstance >> 24;
	int param = gl_BaseInstance & 0xFFFFFF;
	int offset;
	int instance = gl_InstanceID;
	int vertex = gl_VertexID;

	int x, y, z, x2, y2, x3, y3, dx, dy;
	uint c;
	vec2 p, wh;
	vec2 v, ov, pts[4];
	switch (Command) {
	case cmdPicture:
		// Parameters
		offset = 4*instance;
		int m = texelFetch(parameters, param+0+offset).r;
		x = texelFetch(parameters, param+1+offset).r;
		y = texelFetch(parameters, param+2+offset).r;
		z = texelFetch(parameters, param+3+offset).r;
		// Mapping of the picture
		m *= 5;
		Bin = texelFetch(pictureMap, m+0).r;
		UV = vec2(texelFetch(pictureMap, m+1).r, texelFetch(pictureMap, m+2).r);
		wh = vec2(texelFetch(pictureMap, m+3).r, texelFetch(pictureMap, m+4).r);
		// Picture quad
		p = (vec2(x, y) + corners[vertex] * wh) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		UV += corners[vertex] * wh;
		break;

	case cmdText:
	  // Parameters of the whole Print command
		uint f = texelFetch(parameters, param+0).r;
		c = texelFetch(parameters, param+1).r;
		x = texelFetch(parameters, param+2).r;
		y = texelFetch(parameters, param+3).r;
		z = texelFetch(parameters, param+4).r;
		// Parameter for the current character
		offset = 2*instance;
		int r = texelFetch(parameters, param+5+offset).r;
		dx = texelFetch(parameters, param+6+offset).r;
		// Mapping of the current character
		r *= 5;
		Bin = texelFetch(glyphMap, r+0).r;
		UV = vec2(texelFetch(glyphMap, r+1).r, texelFetch(glyphMap, r+2).r);
		wh = vec2(texelFetch(glyphMap, r+3).r, texelFetch(glyphMap, r+4).r);
		// Character quad
		p = (vec2(x+dx, y) + corners[vertex] * wh) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		UV += corners[vertex] * wh;
		ColorIndex = uint(c);
		break;

	case cmdPoint:
		offset = 4*instance;
		// Parameters
		c = texelFetch(parameters, param+0+offset).r;
		x = texelFetch(parameters, param+1+offset).r;
		y = texelFetch(parameters, param+2+offset).r;
		z = texelFetch(parameters, param+3+offset).r;
		// Position
		p = (vec2(x, y) + corners[vertex] * vec2(1.5,1.5)) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		// Color
		ColorIndex = uint(c);
		break;

	case cmdLines:
		offset = 2*instance;
		// Parameters
		c = texelFetch(parameters, param+0).r;
		z = texelFetch(parameters, param+1).r;
		x = texelFetch(parameters, param+2+offset).r;
		y = texelFetch(parameters, param+3+offset).r;
		x2 = texelFetch(parameters, param+4+offset).r;
		y2 = texelFetch(parameters, param+5+offset).r;
		// Position
		dx = x2-x;
		dy = y2-y;
		v = 0.5*normalize(vec2(x2-x, y2-y));
		ov = 0.5*normalize(vec2(-y2+y, x2-x));
		pts = vec2[4](
			vec2(x, y)-v-ov,
			vec2(x2, y2)+v-ov,
			vec2(x, y)-v+ov,
			vec2(x2, y2)+v+ov
		);
		p = (vec2(0.5,0.5) + pts[vertex].xy) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		Orig = vec2(x, y);
		Steep = abs(dx) < abs(dy);
		if (Steep) {
			Slope = float(dx)/float(dy);
		} else {
			Slope = float(dy)/float(dx);
		}
		// Color
		ColorIndex = uint(c);
		break;

	case cmdTriangles:
		offset = 2*gl_VertexID;
		// Parameters
		c = texelFetch(parameters, param+0).r;
		z = texelFetch(parameters, param+1).r;
		x = texelFetch(parameters, param+2+offset).r;
		y = texelFetch(parameters, param+3+offset).r;
		p = (vec2(0.5,0.5) + vec2(x, y)) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		// Color
		ColorIndex = uint(c);
		break;

	}
}
`

// Copyright (c) 2013-2018 Laurent Moussault. All rights reserved.
// Licensed under a simplified BSD license (see LICENSE file).
//------------------------------------------------------------------------------
<commit_msg>Fix bug in line-drawing shader algorithm in pixel<commit_after>package pixel

const vertexShader = "\n" + `#version 460 core

//------------------------------------------------------------------------------

const uint cmdPicture    = 1;
const uint cmdPictureExt = 2;
const uint cmdText       = 3;
const uint cmdPoint      = 4;
const uint cmdLines      = 5;
const uint cmdTriangles  = 6;

const vec2 corners[4] = vec2[4](
	vec2(0, 0),
	vec2(1, 0),
	vec2(0, 1),
	vec2(1, 1)
);

//------------------------------------------------------------------------------

layout(std140, binding = 0) uniform ScreenUBO {
	vec2 PixelSize;
};

//------------------------------------------------------------------------------

layout(binding = 0) uniform isamplerBuffer parameters;
layout(binding = 1) uniform isamplerBuffer pictureMap;
layout(binding = 3) uniform isamplerBuffer glyphMap;

//------------------------------------------------------------------------------

out gl_PerVertex {
	vec4 gl_Position;
};

out PerVertex {
	layout(location=0) flat uint Command;
	layout(location=1) flat uint Bin;
	layout(location=2) vec2 UV;
	layout(location=3) flat uint ColorIndex;
	layout(location=4) flat vec2 Orig;
	layout(location=5) flat float Slope;
	layout(location=6) flat bool Steep;
};

//------------------------------------------------------------------------------

float floatZ(int z) {
	return float(z)/float(0x7FFF);
}

//------------------------------------------------------------------------------

void main(void)
{
	Command = gl_BaseInstance >> 24;
	int param = gl_BaseInstance & 0xFFFFFF;
	int offset;
	int instance = gl_InstanceID;
	int vertex = gl_VertexID;

	int x, y, z, x2, y2, x3, y3, dx, dy;
	uint c;
	vec2 p, wh;
	vec2 t, n, pts[4];
	switch (Command) {
	case cmdPicture:
		// Parameters
		offset = 4*instance;
		int m = texelFetch(parameters, param+0+offset).r;
		x = texelFetch(parameters, param+1+offset).r;
		y = texelFetch(parameters, param+2+offset).r;
		z = texelFetch(parameters, param+3+offset).r;
		// Mapping of the picture
		m *= 5;
		Bin = texelFetch(pictureMap, m+0).r;
		UV = vec2(texelFetch(pictureMap, m+1).r, texelFetch(pictureMap, m+2).r);
		wh = vec2(texelFetch(pictureMap, m+3).r, texelFetch(pictureMap, m+4).r);
		// Picture quad
		p = (vec2(x, y) + corners[vertex] * wh) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		UV += corners[vertex] * wh;
		break;

	case cmdText:
	  // Parameters of the whole Print command
		uint f = texelFetch(parameters, param+0).r;
		c = texelFetch(parameters, param+1).r;
		x = texelFetch(parameters, param+2).r;
		y = texelFetch(parameters, param+3).r;
		z = texelFetch(parameters, param+4).r;
		// Parameter for the current character
		offset = 2*instance;
		int r = texelFetch(parameters, param+5+offset).r;
		dx = texelFetch(parameters, param+6+offset).r;
		// Mapping of the current character
		r *= 5;
		Bin = texelFetch(glyphMap, r+0).r;
		UV = vec2(texelFetch(glyphMap, r+1).r, texelFetch(glyphMap, r+2).r);
		wh = vec2(texelFetch(glyphMap, r+3).r, texelFetch(glyphMap, r+4).r);
		// Character quad
		p = (vec2(x+dx, y) + corners[vertex] * wh) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		UV += corners[vertex] * wh;
		ColorIndex = uint(c);
		break;

	case cmdPoint:
		offset = 4*instance;
		// Parameters
		c = texelFetch(parameters, param+0+offset).r;
		x = texelFetch(parameters, param+1+offset).r;
		y = texelFetch(parameters, param+2+offset).r;
		z = texelFetch(parameters, param+3+offset).r;
		// Position
		p = (vec2(x, y) + corners[vertex] * vec2(1.5,1.5)) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		// Color
		ColorIndex = uint(c);
		break;

	case cmdLines:
		offset = 2*instance;
		// Parameters
		c = texelFetch(parameters, param+0).r;
		z = texelFetch(parameters, param+1).r;
		x = texelFetch(parameters, param+2+offset).r;
		y = texelFetch(parameters, param+3+offset).r;
		x2 = texelFetch(parameters, param+4+offset).r;
		y2 = texelFetch(parameters, param+5+offset).r;
		// Position
		Orig = vec2(x, y);
		dx = x2-x;
		dy = y2-y;
		Steep = abs(dx) < abs(dy);
		t = 0.25*normalize(vec2(dx, dy));
		n = 0.75*normalize(vec2(-dy, dx));
		pts = vec2[4](
			vec2(x, y)+n-t,
			vec2(x, y)-n-t,
			vec2(x2, y2)+n+t,
			vec2(x2, y2)-n+t
		);
		p = (vec2(0.5,0.5) + pts[vertex].xy) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		if (Steep) {
			Slope = float(dx)/float(dy);
		} else {
			Slope = float(dy)/float(dx);
		}
		// Color
		ColorIndex = uint(c);
		break;

	case cmdTriangles:
		offset = 2*gl_VertexID;
		// Parameters
		c = texelFetch(parameters, param+0).r;
		z = texelFetch(parameters, param+1).r;
		x = texelFetch(parameters, param+2+offset).r;
		y = texelFetch(parameters, param+3+offset).r;
		p = (vec2(0.5,0.5) + vec2(x, y)) * PixelSize;
		gl_Position = vec4(p * vec2(2, -2) + vec2(-1,1), floatZ(z), 1);
		// Color
		ColorIndex = uint(c);
		break;

	}
}
`

// Copyright (c) 2013-2018 Laurent Moussault. All rights reserved.
// Licensed under a simplified BSD license (see LICENSE file).
//------------------------------------------------------------------------------
<|endoftext|>


<commit_before>package slave

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/codeskyblue/go-sh"
	"github.com/gobuild/gobuild2/models"
	"github.com/gobuild/gobuild2/pkg/base"
	"github.com/gobuild/gobuild2/pkg/xrpc"
	"github.com/qiniu/log"
)

var (
	TMPDIR     = "./tmp"
	PROGRAM, _ = filepath.Abs(os.Args[0])
	HOSTNAME   = "localhost"
	HOSTINFO   = &xrpc.HostInfo{Os: runtime.GOOS, Arch: runtime.GOARCH, Host: HOSTNAME}
)

func checkError(err error) {
	if err != nil {
		log.Errorf("err: %v", err)
	}
}

type NTMsg struct {
	Status string
	Output string
	Extra  string
}

func work(m *xrpc.Mission) (err error) {
	notify := func(status string, output string, extra ...string) {
		mstatus := &xrpc.MissionStatus{Mid: m.Mid, Status: status,
			Output: output,
			Extra:  strings.Join(extra, ""),
		}
		ok := false
		err := xrpc.Call("UpdateMissionStatus", mstatus, &ok)
		checkError(err)
	}
	defer func() {
		fmt.Println("DONE", err)
		if err != nil {
			notify(models.ST_ERROR, err.Error())
		}
	}()
	// prepare shell session
	sess := sh.NewSession()
	buffer := bytes.NewBuffer(nil)
	sess.Stdout = io.MultiWriter(buffer, os.Stdout)
	sess.Stderr = io.MultiWriter(buffer, os.Stderr)
	sess.ShowCMD = true
	var gopath, _ = filepath.Abs(TMPDIR)
	sess.SetEnv("GOPATH", gopath)
	sess.SetEnv("CGO_ENABLE", "")
	if m.CgoEnable {
		sess.SetEnv("CGO_ENABLE", "1")
	}
	sess.SetEnv("GOOS", m.Os)
	sess.SetEnv("GOARCH", m.Arch)

	var repoAddr = m.Repo
	var cleanRepoName = base.SanitizedRepoPath(repoAddr)

	var srcPath = filepath.Join(gopath, "src", cleanRepoName)

	getsrc := func() (err error) {
		// if err = sess.Command("gopm", "get", "-v", "-u", repoAddr).Run(); err != nil {
		// return
		// }
		if err = sess.Command("gopm", "get", "-g", "-v", repoAddr).Run(); err != nil {
			// if err = sess.Command("gopm", "get", "-v", repoAddr+"@commit:"+m.Sha).Run(); err != nil {
			return
		}
		return nil
	}
	// getsrc = func() (err error) {
	// 	if err = sess.Command("go", "get", "-u", "-v", repoAddr).Run(); err != nil {
	// 		return
	// 	}
	// 	return nil
	// }

	// get source code
	notify(models.ST_RETRIVING, "start get source")
	var done = make(chan bool)
	// var msgQueue = make(chan NTMsg)
	// go func() {
	// quit := make(chan bool)
	// 	for {
	// 		select {
	// 		case msg := <-msgQueue:
	// 			notify(msg.Status, msg.Output, msg.Extra)
	// 		case <- quic:
	// quit <- true
	//			return
	// 		}
	// 	}
	// }()
	// notify message queue
	go func() {
		for {
			select {
			case <-done:
				done <- true
				return
			case <-time.After(2 * time.Second):
				notify(models.ST_RETRIVING, string(buffer.Bytes()))
			}
		}
	}()
	err = getsrc()
	done <- true
	<-done
	notify(models.ST_RETRIVING, string(buffer.Bytes()))
	if err != nil {
		log.Errorf("getsource err: %v", err)
		return
	}
	buffer.Reset()

	extention := "zip"
	var outFile = fmt.Sprintf("%s-%s-%s.%s", filepath.Base(cleanRepoName), m.Os, m.Arch, extention)
	var outFullPath = filepath.Join(srcPath, outFile)

	notify(models.ST_BUILDING, "start building")
	err = sess.Command("gopm", "build", "-u", "-v", sh.Dir(srcPath)).Run()
	if err != nil {
		log.Errorf("gopm build error: %v", err)
		return
	}
	err = sess.Command(PROGRAM, "pack", "--nobuild", "-o", outFile, sh.Dir(srcPath)).Run()
	notify(models.ST_BUILDING, string(buffer.Bytes()))
	if err != nil {
		log.Error(err)
		return
	}

	notify(models.ST_PUBLISHING, "")
	// timestamp := time.Now().Format("20060102-150405")
	var cdnPath = fmt.Sprintf("m%d/%s/raw/%s", m.Mid, cleanRepoName, outFile)
	log.Infof("cdn path: %s", cdnPath)
	var pubAddress string
	if pubAddress, err = UploadQiniu(outFullPath, cdnPath); err != nil {
		checkError(err)
		return
	}
	log.Debugf("publish %s to %s", outFile, pubAddress)
	notify(models.ST_DONE, pubAddress)
	return nil
}

func init() {
	var err error
	HOSTNAME, err = os.Hostname()
	if err != nil {
		log.Fatalf("hostname retrive err: %v", err)
	}
}

func prepare() (err error) {
	qi := new(xrpc.QiniuInfo)
	xrpc.Call("GetQiniuInfo", HOSTINFO, qi)

	initQiniu(qi.AccessKey, qi.SecretKey, qi.Bulket)

	TMPDIR, err = filepath.Abs(TMPDIR)
	if err != nil {
		log.Errorf("tmpdir to abspath err: %v", err)
		return
	}
	if !sh.Test("dir", TMPDIR) {
		os.MkdirAll(TMPDIR, 0755)
	}
	startWork()
	return nil
}

func Action(c *cli.Context) {
	fmt.Println("this is slave daemon")
	webaddr := c.String("webaddr")
	xrpc.DefaultWebAddress = webaddr

	if err := prepare(); err != nil {
		log.Fatalf("slave prepare err: %v", err)
	}
	for {
		mission := &xrpc.Mission{}
		if err := xrpc.Call("GetMission", HOSTINFO, mission); err != nil {
			log.Errorf("get mission failed: %v", err)
			time.Sleep(2 * time.Second)
			continue
		}

		//log.Infof("reply: %v", mission)
		if mission.Idle != 0 {
			//log.Infof("Idle for next reply: %v", mission.Idle)
			fmt.Print(".")
			time.Sleep(mission.Idle)
			continue
		}
		log.Infof("new mission from xrpc: %v", mission)
		missionQueue <- mission
	}
}
<commit_msg>forgot to add output<commit_after>package slave

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"github.com/codegangsta/cli"
	"github.com/codeskyblue/go-sh"
	"github.com/gobuild/gobuild2/models"
	"github.com/gobuild/gobuild2/pkg/base"
	"github.com/gobuild/gobuild2/pkg/xrpc"
	"github.com/qiniu/log"
)

var (
	TMPDIR     = "./tmp"
	PROGRAM, _ = filepath.Abs(os.Args[0])
	HOSTNAME   = "localhost"
	HOSTINFO   = &xrpc.HostInfo{Os: runtime.GOOS, Arch: runtime.GOARCH, Host: HOSTNAME}
)

func checkError(err error) {
	if err != nil {
		log.Errorf("err: %v", err)
	}
}

type NTMsg struct {
	Status string
	Output string
	Extra  string
}

func work(m *xrpc.Mission) (err error) {
	notify := func(status string, output string, extra ...string) {
		mstatus := &xrpc.MissionStatus{Mid: m.Mid, Status: status,
			Output: output,
			Extra:  strings.Join(extra, ""),
		}
		ok := false
		err := xrpc.Call("UpdateMissionStatus", mstatus, &ok)
		checkError(err)
	}
	defer func() {
		fmt.Println("DONE", err)
		if err != nil {
			notify(models.ST_ERROR, err.Error())
		}
	}()
	// prepare shell session
	sess := sh.NewSession()
	buffer := bytes.NewBuffer(nil)
	sess.Stdout = io.MultiWriter(buffer, os.Stdout)
	sess.Stderr = io.MultiWriter(buffer, os.Stderr)
	sess.ShowCMD = true
	var gopath, _ = filepath.Abs(TMPDIR)
	sess.SetEnv("GOPATH", gopath)
	sess.SetEnv("CGO_ENABLE", "")
	if m.CgoEnable {
		sess.SetEnv("CGO_ENABLE", "1")
	}
	sess.SetEnv("GOOS", m.Os)
	sess.SetEnv("GOARCH", m.Arch)

	var repoAddr = m.Repo
	var cleanRepoName = base.SanitizedRepoPath(repoAddr)

	var srcPath = filepath.Join(gopath, "src", cleanRepoName)

	getsrc := func() (err error) {
		// if err = sess.Command("gopm", "get", "-v", "-u", repoAddr).Run(); err != nil {
		// return
		// }
		if err = sess.Command("gopm", "get", "-g", "-v", repoAddr).Run(); err != nil {
			// if err = sess.Command("gopm", "get", "-v", repoAddr+"@commit:"+m.Sha).Run(); err != nil {
			return
		}
		return nil
	}
	// getsrc = func() (err error) {
	// 	if err = sess.Command("go", "get", "-u", "-v", repoAddr).Run(); err != nil {
	// 		return
	// 	}
	// 	return nil
	// }

	// get source code
	notify(models.ST_RETRIVING, "start get source")
	var done = make(chan bool)
	// var msgQueue = make(chan NTMsg)
	// go func() {
	// quit := make(chan bool)
	// 	for {
	// 		select {
	// 		case msg := <-msgQueue:
	// 			notify(msg.Status, msg.Output, msg.Extra)
	// 		case <- quic:
	// quit <- true
	//			return
	// 		}
	// 	}
	// }()
	// notify message queue
	go func() {
		for {
			select {
			case <-done:
				done <- true
				return
			case <-time.After(2 * time.Second):
				notify(models.ST_RETRIVING, string(buffer.Bytes()))
			}
		}
	}()
	err = getsrc()
	done <- true
	<-done
	notify(models.ST_RETRIVING, string(buffer.Bytes()))
	if err != nil {
		log.Errorf("getsource err: %v", err)
		return
	}
	buffer.Reset()

	extention := "zip"
	var outFile = fmt.Sprintf("%s-%s-%s.%s", filepath.Base(cleanRepoName), m.Os, m.Arch, extention)
	var outFullPath = filepath.Join(srcPath, outFile)

	notify(models.ST_BUILDING, "start building")
	err = sess.Command("gopm", "build", "-u", "-v", sh.Dir(srcPath)).Run()
	if err != nil {
		log.Errorf("gopm build error: %v", err)
		notify(models.ST_BUILDING, string(buffer.Bytes()))
		return
	}
	err = sess.Command(PROGRAM, "pack", "--nobuild", "-o", outFile, sh.Dir(srcPath)).Run()
	notify(models.ST_BUILDING, string(buffer.Bytes()))
	if err != nil {
		log.Error(err)
		return
	}

	notify(models.ST_PUBLISHING, "")
	// timestamp := time.Now().Format("20060102-150405")
	var cdnPath = fmt.Sprintf("m%d/%s/raw/%s", m.Mid, cleanRepoName, outFile)
	log.Infof("cdn path: %s", cdnPath)
	var pubAddress string
	if pubAddress, err = UploadQiniu(outFullPath, cdnPath); err != nil {
		checkError(err)
		return
	}
	log.Debugf("publish %s to %s", outFile, pubAddress)
	notify(models.ST_DONE, pubAddress)
	return nil
}

func init() {
	var err error
	HOSTNAME, err = os.Hostname()
	if err != nil {
		log.Fatalf("hostname retrive err: %v", err)
	}
}

func prepare() (err error) {
	qi := new(xrpc.QiniuInfo)
	xrpc.Call("GetQiniuInfo", HOSTINFO, qi)

	initQiniu(qi.AccessKey, qi.SecretKey, qi.Bulket)

	TMPDIR, err = filepath.Abs(TMPDIR)
	if err != nil {
		log.Errorf("tmpdir to abspath err: %v", err)
		return
	}
	if !sh.Test("dir", TMPDIR) {
		os.MkdirAll(TMPDIR, 0755)
	}
	startWork()
	return nil
}

func Action(c *cli.Context) {
	fmt.Println("this is slave daemon")
	webaddr := c.String("webaddr")
	xrpc.DefaultWebAddress = webaddr

	if err := prepare(); err != nil {
		log.Fatalf("slave prepare err: %v", err)
	}
	for {
		mission := &xrpc.Mission{}
		if err := xrpc.Call("GetMission", HOSTINFO, mission); err != nil {
			log.Errorf("get mission failed: %v", err)
			time.Sleep(2 * time.Second)
			continue
		}

		//log.Infof("reply: %v", mission)
		if mission.Idle != 0 {
			//log.Infof("Idle for next reply: %v", mission.Idle)
			fmt.Print(".")
			time.Sleep(mission.Idle)
			continue
		}
		log.Infof("new mission from xrpc: %v", mission)
		missionQueue <- mission
	}
}
<|endoftext|>


<commit_before>package command

const VERSION = "0.9.7"

var (
	GitCommit string
	BuildTime string
)
<commit_msg>Release 0.9.8<commit_after>package command

const VERSION = "0.9.8"

var (
	GitCommit string
	BuildTime string
)
<|endoftext|>


<commit_before>// ***************************************************************************
//
//  Copyright 2017 David (Dizzy) Smith, dizzyd@dizzyd.com
//
//  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 pkg

import (
	"fmt"
	"math"

	"github.com/Jeffail/gabs"
)

type CurseForgeModFile struct {
	projectID  int
	fileID     int
	desc       string
	name       string
	clientOnly bool
}

func SelectCurseForgeModFile(pack *ModPack, mod string, url string, clientOnly bool) error {
	// Try to find the project ID using the mod name as a slug
	projectID, err := pack.db.findModBySlug(mod, pack.modLoader)
	if err != nil {
		return fmt.Errorf("unknown mod %s: %+v", mod, err)
	}

	// Look up the slug, name and description
	_, name, desc, err := pack.db.getProjectInfo(projectID)
	if err != nil {
		return fmt.Errorf("no name/description available for %s (%d): %+v", mod, projectID, err)
	}

	// Setup a mod file entry and then pull the latest file info
	modFile := CurseForgeModFile{projectID: projectID, desc: desc, name: name, clientOnly: clientOnly}
	fileId, err := modFile.getLatestFile(pack.minecraftVersion(), pack.modLoader)
	if err != nil {
		return fmt.Errorf("failed to get latest file for %s (%d): %+v", mod, projectID, err)
	}

	// If we found a newer file, update entry and then the pack
	if fileId > modFile.fileID {
		modFile.fileID = fileId
		err = pack.selectMod(&modFile)
		if err != nil {
			return err
		}
	}

	return nil
}

func NewCurseForgeModFile(modJson *gabs.Container) *CurseForgeModFile {
	projectID, _ := intValue(modJson, "projectID")
	fileID, _ := intValue(modJson, "fileID")
	name, ok := modJson.Path("desc").Data().(string)
	if !ok {
		name = fmt.Sprintf("Curseforge project %d: %d", projectID, fileID)
	}
	clientOnly, ok := modJson.S("clientOnly").Data().(bool)
	return &CurseForgeModFile{projectID, fileID, name, name, ok && clientOnly}
}

func (f CurseForgeModFile) install(pack *ModPack) error {
	// Check the mod cache to see if we already have the right file ID installed
	lastFileId, lastFilename := pack.modCache.GetLastModFile(f.projectID)
	if lastFileId == f.fileID {
		// Nothing to do; we can skip this installed file
		fmt.Printf("Skipping %s\n", lastFilename)
		return nil
	} else if lastFileId > 0 {
		// A different version of the file is installed; clean it up
		pack.modCache.CleanupModFile(f.projectID)
	}

	// Resolve the project ID into a slug
	slug, err := pack.db.findSlugByProject(f.projectID)
	if err != nil {
		return fmt.Errorf("failed to find slug for project %d: %+v", f.projectID, err)
	}

	// Now, retrieve the JSON descriptor for this file so we can get the CDN url
	descriptorUrl := fmt.Sprintf("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", f.projectID, f.fileID)
	descriptor, err := getJSONFromURL(descriptorUrl)
	if err != nil {
		return fmt.Errorf("failed to retrieve descriptor for %s: %+v", slug, err)
	}

	// Download the file to the pack mod directory
	finalUrl := descriptor.Path("downloadUrl").Data().(string)

	filename, err := downloadHttpFileToDir(finalUrl, pack.modPath(), true)
	if err != nil {
		return err
	}

	// Download succeeded; register this mod as installed in the cache
	pack.modCache.AddModFile(f.projectID, f.fileID, filename)
	return nil
}

func (f *CurseForgeModFile) update(pack *ModPack) (bool, error) {
	latestFile, err := f.getLatestFile(pack.minecraftVersion(), pack.modLoader)
	if err != nil {
		return false, err
	}

	if latestFile > f.fileID {
		f.fileID = latestFile
		return true, nil
	}

	return false, nil
}

func (f CurseForgeModFile) getName() string {
	return f.name
}

func (f CurseForgeModFile) isClientOnly() bool {
	return f.clientOnly
}

func (f CurseForgeModFile) equalsJson(modJson *gabs.Container) bool {
	projectID, ok := modJson.Path("projectID").Data().(float64)
	return ok && int(projectID) == f.projectID
}

func (f CurseForgeModFile) toJson() map[string]interface{} {
	result := map[string]interface{}{
		"projectID": f.projectID,
		"fileID":    f.fileID,
		"required":  true,
		"desc":      f.name,
	}
	if f.clientOnly {
		result["clientOnly"] = true
	}
	return result
}

func (f CurseForgeModFile) getLatestFile(minecraftVersion string, modLoader string) (int, error) {
	// Pull the project's descriptor, which has a list of the latest files for each version of Minecraft
	projectUrl := fmt.Sprintf("https://addons-ecs.forgesvc.net/api/v2/addon/%d", f.projectID)
	project, err := getJSONFromURL(projectUrl)
	if err != nil {
		return -1, fmt.Errorf("failed to retrieve project for %s: %+v", f.name, err)
	}

	selectedFileType := math.MaxInt8
	selectedFileId := 0

	// Look for the file with the matching version
	files, _ := project.Path("gameVersionLatestFiles").Children()
	for _, file := range files {
		fileType, _ := intValue(file, "fileType") // 1 = release, 2 = beta, 3 = alpha
		fileId, _ := intValue(file, "projectFileId")
		modLoaderId, _ := intValue(file, "modLoader")
		targetVsn := file.Path("gameVersion").Data().(string)

		if targetVsn != minecraftVersion {
			continue
		}

		if modLoaderId == 1 && modLoader != "forge" {
			continue
		}

		if modLoaderId == 4 && modLoader != "fabric" {
			continue
		}

		// Matched on version; prefer releases over beta/alpha
		if fileType < selectedFileType {
			selectedFileType = fileType
			selectedFileId = fileId
		}
	}

	if selectedFileId == 0 {
		return -1, fmt.Errorf("no version found for Minecraft %s\n", minecraftVersion)
	}

	// TODO: Pull file descriptor and check for deps
	return selectedFileId, nil
}

func PrintCurseForgeModInfo(projectId int) error {
	// Pull the project's descriptor, which has a list of the latest files for each version of Minecraft
	projectUrl := fmt.Sprintf("https://addons-ecs.forgesvc.net/api/v2/addon/%d", projectId)
	project, err := getJSONFromURL(projectUrl)
	if err != nil {
		return fmt.Errorf("failed to retrieve project %d: %+v", projectId, err)
	}

	name, _ := strValue(project, "name")
	slug, _ := strValue(project, "slug")
	summary, _ := strValue(project, "summary")

	fmt.Printf("%s (%s)\n  %s\nFiles:\n", name, slug, summary)

	// List recent files
	files, _ := project.Path("gameVersionLatestFiles").Children()
	for _, file := range files {
		filename, _ := strValue(file, "projectFileName")
		fileType, _ := intValue(file, "fileType") // 1 = release, 2 = beta, 3 = alpha
		modLoaderId, _ := intValue(file, "modLoader") // 1 == forge, 4 == fabric
		targetVsn, _ := strValue(file, "gameVersion")

		var releaseType string
		switch fileType {
		case 1:
			releaseType = "release"
		case 2:
			releaseType = "beta"
		case 3:
			releaseType = "alpha"
		default:
			releaseType = "unknown-release"
		}

		var modLoader string
		switch modLoaderId {
		case 1:
			modLoader = "forge"
		case 4:
			modLoader = "fabric"
		default:
			modLoader = "forge"
		}

		fmt.Printf("* %s for Minecraft %s, %s, %s\n", filename, targetVsn, modLoader, releaseType)
	}

	return nil;
}
<commit_msg>Add retry loop for updates to CF files<commit_after>// ***************************************************************************
//
//  Copyright 2017 David (Dizzy) Smith, dizzyd@dizzyd.com
//
//  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 pkg

import (
	"fmt"
	"math"

	"github.com/Jeffail/gabs"
)

type CurseForgeModFile struct {
	projectID  int
	fileID     int
	desc       string
	name       string
	clientOnly bool
}

func SelectCurseForgeModFile(pack *ModPack, mod string, url string, clientOnly bool) error {
	// Try to find the project ID using the mod name as a slug
	projectID, err := pack.db.findModBySlug(mod, pack.modLoader)
	if err != nil {
		return fmt.Errorf("unknown mod %s: %+v", mod, err)
	}

	// Look up the slug, name and description
	_, name, desc, err := pack.db.getProjectInfo(projectID)
	if err != nil {
		return fmt.Errorf("no name/description available for %s (%d): %+v", mod, projectID, err)
	}

	// Setup a mod file entry and then pull the latest file info
	modFile := CurseForgeModFile{projectID: projectID, desc: desc, name: name, clientOnly: clientOnly}
	fileId, err := modFile.getLatestFile(pack.minecraftVersion(), pack.modLoader)
	if err != nil {
		return fmt.Errorf("failed to get latest file for %s (%d): %+v", mod, projectID, err)
	}

	// If we found a newer file, update entry and then the pack
	if fileId > modFile.fileID {
		modFile.fileID = fileId
		err = pack.selectMod(&modFile)
		if err != nil {
			return err
		}
	}

	return nil
}

func NewCurseForgeModFile(modJson *gabs.Container) *CurseForgeModFile {
	projectID, _ := intValue(modJson, "projectID")
	fileID, _ := intValue(modJson, "fileID")
	name, ok := modJson.Path("desc").Data().(string)
	if !ok {
		name = fmt.Sprintf("Curseforge project %d: %d", projectID, fileID)
	}
	clientOnly, ok := modJson.S("clientOnly").Data().(bool)
	return &CurseForgeModFile{projectID, fileID, name, name, ok && clientOnly}
}

func (f CurseForgeModFile) install(pack *ModPack) error {
	// Check the mod cache to see if we already have the right file ID installed
	lastFileId, lastFilename := pack.modCache.GetLastModFile(f.projectID)
	if lastFileId == f.fileID {
		// Nothing to do; we can skip this installed file
		fmt.Printf("Skipping %s\n", lastFilename)
		return nil
	} else if lastFileId > 0 {
		// A different version of the file is installed; clean it up
		pack.modCache.CleanupModFile(f.projectID)
	}

	// Resolve the project ID into a slug
	slug, err := pack.db.findSlugByProject(f.projectID)
	if err != nil {
		return fmt.Errorf("failed to find slug for project %d: %+v", f.projectID, err)
	}

	// Now, retrieve the JSON descriptor for this file so we can get the CDN url
	descriptorUrl := fmt.Sprintf("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", f.projectID, f.fileID)
	descriptor, err := getJSONFromURL(descriptorUrl)
	if err != nil {
		return fmt.Errorf("failed to retrieve descriptor for %s: %+v", slug, err)
	}

	// Download the file to the pack mod directory
	finalUrl := descriptor.Path("downloadUrl").Data().(string)

	filename, err := downloadHttpFileToDir(finalUrl, pack.modPath(), true)
	if err != nil {
		return err
	}

	// Download succeeded; register this mod as installed in the cache
	pack.modCache.AddModFile(f.projectID, f.fileID, filename)
	return nil
}

func (f *CurseForgeModFile) update(pack *ModPack) (bool, error) {
	latestFile, err := f.getLatestFile(pack.minecraftVersion(), pack.modLoader)
	if err != nil {
		return false, err
	}

	if latestFile > f.fileID {
		f.fileID = latestFile
		return true, nil
	}

	return false, nil
}

func (f CurseForgeModFile) getName() string {
	return f.name
}

func (f CurseForgeModFile) isClientOnly() bool {
	return f.clientOnly
}

func (f CurseForgeModFile) equalsJson(modJson *gabs.Container) bool {
	projectID, ok := modJson.Path("projectID").Data().(float64)
	return ok && int(projectID) == f.projectID
}

func (f CurseForgeModFile) toJson() map[string]interface{} {
	result := map[string]interface{}{
		"projectID": f.projectID,
		"fileID":    f.fileID,
		"required":  true,
		"desc":      f.name,
	}
	if f.clientOnly {
		result["clientOnly"] = true
	}
	return result
}

func (f CurseForgeModFile) getLatestFile(minecraftVersion string, modLoader string) (int, error) {
	// Setup a retry counter to deal with long timeouts (a recent problem)
	retryCount := 3

	// Pull the project's descriptor, which has a list of the latest files for each version of Minecraft
	retry:
		projectUrl := fmt.Sprintf("https://addons-ecs.forgesvc.net/api/v2/addon/%d", f.projectID)
		project, err := getJSONFromURL(projectUrl)
		if err != nil {
			if retryCount > 0 {
				fmt.Printf("Retrying update check for %s (%s)\n", f.name, projectUrl)
				retryCount -= 1
				goto retry
			} else {
				return -1, fmt.Errorf("failed to retrieve project for %s: %+v", f.name, err)
			}
		}

	selectedFileType := math.MaxInt8
	selectedFileId := 0

	// Look for the file with the matching version
	files, _ := project.Path("gameVersionLatestFiles").Children()
	for _, file := range files {
		fileType, _ := intValue(file, "fileType") // 1 = release, 2 = beta, 3 = alpha
		fileId, _ := intValue(file, "projectFileId")
		modLoaderId, _ := intValue(file, "modLoader")
		targetVsn := file.Path("gameVersion").Data().(string)

		if targetVsn != minecraftVersion {
			continue
		}

		if modLoaderId == 1 && modLoader != "forge" {
			continue
		}

		if modLoaderId == 4 && modLoader != "fabric" {
			continue
		}

		// Matched on version; prefer releases over beta/alpha
		if fileType < selectedFileType {
			selectedFileType = fileType
			selectedFileId = fileId
		}
	}

	if selectedFileId == 0 {
		return -1, fmt.Errorf("no version found for Minecraft %s\n", minecraftVersion)
	}

	// TODO: Pull file descriptor and check for deps
	return selectedFileId, nil
}

func PrintCurseForgeModInfo(projectId int) error {
	// Pull the project's descriptor, which has a list of the latest files for each version of Minecraft
	projectUrl := fmt.Sprintf("https://addons-ecs.forgesvc.net/api/v2/addon/%d", projectId)
	project, err := getJSONFromURL(projectUrl)
	if err != nil {
		return fmt.Errorf("failed to retrieve project %d: %+v", projectId, err)
	}

	name, _ := strValue(project, "name")
	slug, _ := strValue(project, "slug")
	summary, _ := strValue(project, "summary")

	fmt.Printf("%s (%s)\n  %s\nFiles:\n", name, slug, summary)

	// List recent files
	files, _ := project.Path("gameVersionLatestFiles").Children()
	for _, file := range files {
		filename, _ := strValue(file, "projectFileName")
		fileType, _ := intValue(file, "fileType") // 1 = release, 2 = beta, 3 = alpha
		modLoaderId, _ := intValue(file, "modLoader") // 1 == forge, 4 == fabric
		targetVsn, _ := strValue(file, "gameVersion")

		var releaseType string
		switch fileType {
		case 1:
			releaseType = "release"
		case 2:
			releaseType = "beta"
		case 3:
			releaseType = "alpha"
		default:
			releaseType = "unknown-release"
		}

		var modLoader string
		switch modLoaderId {
		case 1:
			modLoader = "forge"
		case 4:
			modLoader = "fabric"
		default:
			modLoader = "forge"
		}

		fmt.Printf("* %s for Minecraft %s, %s, %s\n", filename, targetVsn, modLoader, releaseType)
	}

	return nil;
}
<|endoftext|>