<commit_before>package main

import (
	"fmt"
	"time"
	"os"
	"bufio"
	"math/rand"
	"math"
	"runtime"
	"github.com/cloudfoundry/gosigar"
	"io/ioutil"
	"path"
	"io"
	"bytes"
	"flag"
	"log"
)

const Blocksize = 0x1 << 16 // 65,536 bytes, 2^16 bytes

func main() {
	var bonnieTempDir, bonnieParentDir, bonnieDir string
	var numProcs int
	var verbose bool
	bonnieTempDir, err := ioutil.TempDir("", "bonniegoParent")
	check(err)
	defer os.RemoveAll(bonnieTempDir)

	flag.BoolVar(&verbose, "v", false, "Verbose. Will print to stderr diagnostic information such as the amount of RAM, number of cores, etc.")
	flag.IntVar(&numProcs, "procs", runtime.NumCPU(), "The number of concurrent readers/writers, defaults to the number of CPU cores")
	flag.StringVar(&bonnieParentDir, "dir", bonnieTempDir, "The directory in which bonniego places its temp files, should have at least twice system RAM available")
	flag.Parse()

	// if bonnieParentDir exists, e.g. "/tmp", all is good, but if it doesn't, e.g. "/tmp/bonniego_run_five", then create it
	fileInfo, err := os.Stat(bonnieParentDir)
	if err != nil {
		err = os.Mkdir(bonnieParentDir, 0755)
		check(err)
		defer os.RemoveAll(bonnieParentDir)
	}
	if ! fileInfo.IsDir() {
		panic(fmt.Sprintf("'%s' is not a directory!", bonnieParentDir))
	}

	bonnieDir = path.Join(bonnieParentDir, "bonniego")
	err = os.Mkdir(bonnieDir, 0755)
	check(err)
	defer os.RemoveAll(bonnieDir)

	mem := sigar.Mem{}
	mem.Get()
	if verbose {
		log.Printf("Bonnie working directory: %s\n", bonnieDir)
		log.Printf("Number of concurrent processes: %d\n", numProcs)
		log.Printf("Total System RAM (MiB): %d\n", mem.Total>>20)
	}

	fileSize := int(mem.Total) * 2 / numProcs
	//fileSize = fileSize >> 5 // fixme: comment-out before committing. during testing; speeds up tests thirty-two-fold

	// randomBlock has random data to prevent filesystems which use compression (e.g. ZFS) from having an unfair advantage
	// we're testing hardware throughput, not filesystem throughput
	randomBlock := make([]byte, Blocksize)
	lenRandom, err := rand.Read(randomBlock)
	check(err)
	if len(randomBlock) != lenRandom {
		panic("RandomBlock didn't get the correct number of bytes")
	}

	start := time.Now()

	bytesReadorWritten := make(chan int)

	for i := 0; i < numProcs; i++ {
		go testWritePerformance(path.Join(bonnieDir, fmt.Sprintf("bonnie.%d", i)), fileSize, randomBlock, bytesReadorWritten)
	}
	bytesWritten := 0
	for i := 0; i < numProcs; i++ {
		bytesWritten += <-bytesReadorWritten
	}

	finish := time.Now()
	duration := finish.Sub(start)
	if verbose {
		log.Printf("Written (MiB): %d\n", bytesWritten>>20)
		log.Printf("Duration (seconds): %f\n", duration.Seconds())
	}
	fmt.Printf("Sequential Write MiB/s: %0.2f\n", float64(bytesWritten)/float64(duration.Seconds())/math.Exp2(20))

	start = time.Now()

	for i := 0; i < numProcs; i++ {
		go testReadPerformance(path.Join(bonnieDir, fmt.Sprintf("bonnie.%d", i)), randomBlock, bytesReadorWritten)
	}
	bytesRead := 0
	for i := 0; i < numProcs; i++ {
		bytesRead += <-bytesReadorWritten
	}

	finish = time.Now()
	duration = finish.Sub(start)

	if verbose {
		log.Printf("Read (MiB): %d\n", bytesRead>>20)
		log.Printf("Duration (seconds): %f\n", duration.Seconds())
	}
	fmt.Printf("Sequential Read MiB/s: %0.2f\n", float64(bytesWritten)/float64(duration.Seconds())/math.Exp2(20))

	numOperationsChan := make(chan int)
	start = time.Now()

	for i := 0; i < numProcs; i++ {
		go testIOPerformance(path.Join(bonnieDir, fmt.Sprintf("bonnie.%d", i)), numOperationsChan)
	}
	numOperations := 0
	for i := 0; i < numProcs; i++ {
		numOperations += <-numOperationsChan
	}

	finish = time.Now()
	duration = finish.Sub(start)

	if verbose {
		log.Printf("operations %d\n", numOperations)
		log.Printf("Duration (seconds): %f\n", duration.Seconds())
	}
	fmt.Printf("IOPS: %0.0f\n", float64(numOperations)/float64(duration.Seconds()))
}

func testWritePerformance(filename string, fileSize int, randomBlock []byte, bytesWrittenChannel chan<- int) {
	f, err := os.Create(filename)
	check(err)
	defer f.Close()

	w := bufio.NewWriter(f)

	bytesWritten := 0
	for i := 0; i < fileSize; i += len(randomBlock) {
		n, err := w.Write(randomBlock)
		check(err)
		bytesWritten += n
	}

	w.Flush()
	f.Close()
	bytesWrittenChannel <- bytesWritten
}

func testReadPerformance(filename string, randomBlock []byte, bytesReadChannel chan<- int) {
	f, err := os.Open(filename)
	check(err)
	defer f.Close()

	bytesRead := 0
	data := make([]byte, Blocksize)

	for {
		n, err := f.Read(data)
		bytesRead += n
		if err != nil {
			if err == io.EOF {
				break
			}
			panic(err)
		}
		// once every 127 blocks, do a sanity check. 127 is prime to avoid collisions
		// e.g. 128 would check every block, not every 128th block
		if bytesRead%127 == 0 {
			if ! bytes.Equal(randomBlock, data) {
				panic("last block didn't match")
			}
		}
	}

	bytesReadChannel <- bytesRead
	f.Close()
}

func testIOPerformance(filename string, numOpsChannel chan<- int) {
	diskBlock := 512 // in the olden days, disk blocks were 512 bytes
	fileInfo, err := os.Stat(filename)
	check(err)
	fileSize := fileInfo.Size() - int64(diskBlock) // give myself room to not read past EOF
	numOperations := 0x1 << 16                     // a fancy way of saying 65,536

	f, err := os.OpenFile(filename, os.O_RDWR, 0644)
	check(err)
	defer f.Close()

	data := make([]byte, diskBlock)
	checksum := make([]byte, diskBlock)

	for i := 0; i < numOperations; i++ {
		f.Seek(rand.Int63n(fileSize), 0)
		// TPC-E has a reads:writes ratio of 9.7:1  http://www.cs.cmu.edu/~chensm/papers/TPCE-sigmod-record10.pdf
		// we round to 10:1
		if i%10 != 0 {
			length, err := f.Read(data)
			check(err)
			if length != diskBlock {
				panic(fmt.Sprintf("I expected to read %d bytes, instead I read %d bytes!", diskBlock, length))
			}
			for i := 0; i < diskBlock; i++ {
				checksum[i] ^= data[i]
			}
		} else {
			length, err := f.Write(checksum)
			check(err)
			if length != diskBlock {
				panic(fmt.Sprintf("I expected to write %d bytes, instead I wrote %d bytes!", diskBlock, length))
			}
		}
	}
	numOpsChannel <- int(numOperations)
	f.Close()
}

func check(e error) {
	if e != nil {
		panic(e)
	}
}
<commit_msg>IOPS-measurement is time-based rather than number-of-operations-based<commit_after>package main

import (
	"fmt"
	"time"
	"os"
	"bufio"
	"math/rand"
	"math"
	"runtime"
	"github.com/cloudfoundry/gosigar"
	"io/ioutil"
	"path"
	"io"
	"bytes"
	"flag"
	"log"
)

const Blocksize = 0x1 << 16 // 65,536 bytes, 2^16 bytes

func main() {
	var bonnieTempDir, bonnieParentDir, bonnieDir string
	var numProcs int
	var verbose bool
	bonnieTempDir, err := ioutil.TempDir("", "bonniegoParent")
	check(err)
	defer os.RemoveAll(bonnieTempDir)

	flag.BoolVar(&verbose, "v", false, "Verbose. Will print to stderr diagnostic information such as the amount of RAM, number of cores, etc.")
	flag.IntVar(&numProcs, "procs", runtime.NumCPU(), "The number of concurrent readers/writers, defaults to the number of CPU cores")
	flag.StringVar(&bonnieParentDir, "dir", bonnieTempDir, "The directory in which bonniego places its temp files, should have at least twice system RAM available")
	flag.Parse()

	// if bonnieParentDir exists, e.g. "/tmp", all is good, but if it doesn't, e.g. "/tmp/bonniego_run_five", then create it
	fileInfo, err := os.Stat(bonnieParentDir)
	if err != nil {
		err = os.Mkdir(bonnieParentDir, 0755)
		check(err)
		defer os.RemoveAll(bonnieParentDir)
	}
	if ! fileInfo.IsDir() {
		panic(fmt.Sprintf("'%s' is not a directory!", bonnieParentDir))
	}

	bonnieDir = path.Join(bonnieParentDir, "bonniego")
	err = os.Mkdir(bonnieDir, 0755)
	check(err)
	defer os.RemoveAll(bonnieDir)

	mem := sigar.Mem{}
	mem.Get()
	if verbose {
		log.Printf("Bonnie working directory: %s\n", bonnieDir)
		log.Printf("Number of concurrent processes: %d\n", numProcs)
		log.Printf("Total System RAM (MiB): %d\n", mem.Total>>20)
	}

	fileSize := int(mem.Total) * 2 / numProcs
	//fileSize = fileSize >> 5 // fixme: comment-out before committing. during testing; speeds up tests thirty-two-fold

	// randomBlock has random data to prevent filesystems which use compression (e.g. ZFS) from having an unfair advantage
	// we're testing hardware throughput, not filesystem throughput
	randomBlock := make([]byte, Blocksize)
	lenRandom, err := rand.Read(randomBlock)
	check(err)
	if len(randomBlock) != lenRandom {
		panic("RandomBlock didn't get the correct number of bytes")
	}

	start := time.Now()

	bytesReadorWritten := make(chan int)

	for i := 0; i < numProcs; i++ {
		go testWritePerformance(path.Join(bonnieDir, fmt.Sprintf("bonnie.%d", i)), fileSize, randomBlock, bytesReadorWritten)
	}
	bytesWritten := 0
	for i := 0; i < numProcs; i++ {
		bytesWritten += <-bytesReadorWritten
	}

	finish := time.Now()
	duration := finish.Sub(start)
	if verbose {
		log.Printf("Written (MiB): %d\n", bytesWritten>>20)
		log.Printf("Duration (seconds): %f\n", duration.Seconds())
	}
	fmt.Printf("Sequential Write MiB/s: %0.2f\n", float64(bytesWritten)/float64(duration.Seconds())/math.Exp2(20))

	start = time.Now()

	for i := 0; i < numProcs; i++ {
		go testReadPerformance(path.Join(bonnieDir, fmt.Sprintf("bonnie.%d", i)), randomBlock, bytesReadorWritten)
	}
	bytesRead := 0
	for i := 0; i < numProcs; i++ {
		bytesRead += <-bytesReadorWritten
	}

	finish = time.Now()
	duration = finish.Sub(start)

	if verbose {
		log.Printf("Read (MiB): %d\n", bytesRead>>20)
		log.Printf("Duration (seconds): %f\n", duration.Seconds())
	}
	fmt.Printf("Sequential Read MiB/s: %0.2f\n", float64(bytesWritten)/float64(duration.Seconds())/math.Exp2(20))

	numOperationsChan := make(chan int)
	start = time.Now()

	for i := 0; i < numProcs; i++ {
		go testIOPerformance(path.Join(bonnieDir, fmt.Sprintf("bonnie.%d", i)), numOperationsChan)
	}
	numOperations := 0
	for i := 0; i < numProcs; i++ {
		numOperations += <-numOperationsChan
	}

	finish = time.Now()
	duration = finish.Sub(start)

	if verbose {
		log.Printf("operations %d\n", numOperations)
		log.Printf("Duration (seconds): %f\n", duration.Seconds())
	}
	fmt.Printf("IOPS: %0.0f\n", float64(numOperations)/float64(duration.Seconds()))
}

func testWritePerformance(filename string, fileSize int, randomBlock []byte, bytesWrittenChannel chan<- int) {
	f, err := os.Create(filename)
	check(err)
	defer f.Close()

	w := bufio.NewWriter(f)

	bytesWritten := 0
	for i := 0; i < fileSize; i += len(randomBlock) {
		n, err := w.Write(randomBlock)
		check(err)
		bytesWritten += n
	}

	w.Flush()
	f.Close()
	bytesWrittenChannel <- bytesWritten
}

func testReadPerformance(filename string, randomBlock []byte, bytesReadChannel chan<- int) {
	f, err := os.Open(filename)
	check(err)
	defer f.Close()

	bytesRead := 0
	data := make([]byte, Blocksize)

	for {
		n, err := f.Read(data)
		bytesRead += n
		if err != nil {
			if err == io.EOF {
				break
			}
			panic(err)
		}
		// once every 127 blocks, do a sanity check. 127 is prime to avoid collisions
		// e.g. 128 would check every block, not every 128th block
		if bytesRead%127 == 0 {
			if ! bytes.Equal(randomBlock, data) {
				panic("last block didn't match")
			}
		}
	}

	bytesReadChannel <- bytesRead
	f.Close()
}

func testIOPerformance(filename string, numOpsChannel chan<- int) {
	diskBlockSize := 0x1 << 9 // 512 bytes, nostalgia: in the olden (System V) days, disk blocks were 512 bytes
	fileInfo, err := os.Stat(filename)
	check(err)
	fileSizeLessOneDiskBlock := fileInfo.Size() - int64(diskBlockSize) // give myself room to not read past EOF
	numOperations := 0x1 << 16                                         // a fancy way of saying 65,536

	f, err := os.OpenFile(filename, os.O_RDWR, 0644)
	check(err)
	defer f.Close()

	data := make([]byte, diskBlockSize)
	checksum := make([]byte, diskBlockSize)

	start := time.Now()
	for i := 0; time.Now().Sub(start).Seconds() < 15.0; i++ { // run for 15 seconds then blow this taco joint
		f.Seek(rand.Int63n(fileSizeLessOneDiskBlock), 0)
		// TPC-E has a reads:writes ratio of 9.7:1  http://www.cs.cmu.edu/~chensm/papers/TPCE-sigmod-record10.pdf
		// we round to 10:1
		if i%10 != 0 {
			length, err := f.Read(data)
			check(err)
			if length != diskBlockSize {
				panic(fmt.Sprintf("I expected to read %d bytes, instead I read %d bytes!", diskBlockSize, length))
			}
			for j := 0; j < diskBlockSize; j++ {
				checksum[j] ^= data[j]
			}
		} else {
			length, err := f.Write(checksum)
			check(err)
			if length != diskBlockSize {
				panic(fmt.Sprintf("I expected to write %d bytes, instead I wrote %d bytes!", diskBlockSize, length))
			}
		}
	}
	numOpsChannel <- int(numOperations)
	f.Close()
}

func check(e error) {
	if e != nil {
		panic(e)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"periph.io/x/periph/conn/gpio"
	"periph.io/x/periph/conn/gpio/gpioreg"
	"periph.io/x/periph/host"
	"github.com/rs/cors"
)

var (
	mode     = 2
	demoMode = false
	isPushoverEnabled = false

	// LEDs
	ledRedPin    = 4
	ledYellowPin = 17
	ledGreenPin  = 27
	ledToColor   = map[int]string{}
	ledMap = map[int]gpio.PinIO{}
	ledMode 		 = 0

	// Buttons
	buttonPin 	= 22
	buttons 		= map[int]gpio.PinIO{}

	// GPIOs
	gpios = map[int]Gpio{}
	demoNum = 26

	// Pushover
	pushoverUser = ""
	pushoverToken = ""
	pushoverApi = "https://api.pushover.net:443/1/messages.json"
)

func getLEDString(color string) string {
	return "Toggle " + strings.ToUpper(color)
}

func setGpio(id int, name string, value int) {
	gpios[id] = Gpio{id, name, value}
}

func toggleLED(id int, pin gpio.PinIO, color string) {
	//fmt.Println(getLEDString(color))
	if !demoMode {
		//fmt.Println("Val to write", !pin.Read())
		pin.Out(!pin.Read())
		value := 0
		if pin.Read() == gpio.High {
			value = 1
		}
		setGpio(id, pin.Name(), value)
	}
}

func initButtons() {
	buttons[0] = gpioreg.ByName(strconv.Itoa(buttonPin))
	if err := buttons[0].In(gpio.PullDown, gpio.BothEdges); err != nil {
  	log.Fatal(err)
  }
	for buttonIndex := 0; buttonIndex < len(buttons); buttonIndex++ {
		fmt.Printf("%s: %s\n", buttons[buttonIndex], buttons[buttonIndex].Function())
	}
}

func initLEDs() {
	if mode == 2 {
		ledMap[0] = gpioreg.ByName(strconv.Itoa(ledRedPin))
		ledMap[1] = gpioreg.ByName(strconv.Itoa(ledYellowPin))
		ledMap[2] = gpioreg.ByName(strconv.Itoa(ledGreenPin))
		for i := 0; i < len(ledMap); i++ {
			fmt.Println("Resetting ", ledMap[i])
			if err := ledMap[i].Out(gpio.Low); err != nil {
        log.Fatal(err)
      }
		}
	}
	initLEDcolors()
}

func initLEDcolors() {
	ledToColor[0] = "red"
	ledToColor[1] = "yellow"
	ledToColor[2] = "green"
}

func initGPIO() error {
	_,err := host.Init()
	return err
}

func doLedToggling(i int, isSleepEnabled bool) {
	if !demoMode {
	  if mode == 2{
			toggleLED(i%3, ledMap[i%3], ledToColor[i%3])
		}
	} else {
		fmt.Println(getLEDString(ledToColor[i%3]))
		setGpio(i%demoNum, "GPIO" + strconv.Itoa(i%demoNum), i % 2)
	}

	if isSleepEnabled {
		time.Sleep(time.Second)
	}
}

type Gpio struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Value int    `json:"value"`
}

func getGpios(w http.ResponseWriter, r *http.Request) {
	gpiosToJson := make([]Gpio, 0, len(gpios))
	fmt.Printf("Transforming %d GPIOs\n", len(gpios))
	for  _, value := range gpios {
  	gpiosToJson = append(gpiosToJson, value)
	}
	fmt.Printf("Transformed into %d gpios\n", len(gpiosToJson))
	json, err := json.Marshal(gpiosToJson)

	if err != nil {
		panic(err)
	}
	w.Write(json)
}

type PushoverMessage struct {
	Token string `json:"token"`
	User string `json:"user"`
	Message string `json:"message"`
}

func toggleLedMode() {
	if ledMode == 0 {
		ledMode = 1
	} else {
		ledMode = 0
	}
}

func ledModeHandler(w http.ResponseWriter, r *http.Request) {
	toggleLedMode()

	if isPushoverEnabled {
		message := PushoverMessage{pushoverToken, pushoverUser, "LED mode toggled"}
		json, err := json.Marshal(message)
		var jsonStr = []byte(json)

		// Build the request
		client := &http.Client{}
		req, err := http.NewRequest("POST", pushoverApi, bytes.NewBuffer(jsonStr))
		if err != nil {
			fmt.Println("Request ERROR:", err)
			return
		}

	  req.Header.Add("Content-Type", "application/json")
		resp, err := client.Do(req)
		if err != nil {
			fmt.Println("Response ERROR:", err)
			return
		}
		defer resp.Body.Close()
		fmt.Println("Response: ", *resp)
	}
}

func initWebServer() {
	fmt.Println("Initializing Webserver")
	mux := http.NewServeMux()
	mux.HandleFunc("/v1/gpios", getGpios)
	mux.HandleFunc("/v1/ledMode", ledModeHandler)
	handler := cors.Default().Handler(mux)
	http.ListenAndServe(":8080", handler)
}

func listenForButtonsPress() {
	fmt.Println("Listening for button presses")
	fmt.Println("Check buttons")
	for {
		fmt.Println("Check button ", buttons[0])
		buttons[0].WaitForEdge(-1)
		fmt.Printf("-> %s\n", buttons[0].Read())
	}
}

func main() {
	fmt.Println("Parsing parameters")
	num := flag.Int("num", 0, "number of blinks")
	buttonEnabled := flag.Bool("button", false, "button mode")
	api := flag.Bool("api", true, "API enabled")
	demo := flag.Bool("demo", false, "Demo mode enabled")
	pushoverFromCli := flag.Bool("pushover", false, "Pushover notifications enabled")

	flag.Parse()

	demoMode = *demo
	isPushoverEnabled = *pushoverFromCli
	pushoverUser = os.Getenv("PUSHOVER_USER")
	pushoverToken = os.Getenv("PUSHOVER_TOKEN")

	if isPushoverEnabled && (pushoverUser == "" || pushoverToken == "") {
		fmt.Println("Pushover env variables are undefined, disabling pushover notifs")
		isPushoverEnabled = false
	}

	if demoMode {
		fmt.Println("Running in demo mode, no physical hw interaction")
	}

	fmt.Println("Number of blinks:", *num)

	if *api {
		go initWebServer()
	}

	if !demoMode {
		var err = initGPIO()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		initLEDs()
		initButtons()
		if *buttonEnabled {
			go listenForButtonsPress()
		}
	} else {
		initLEDcolors()
	}

	if *num == 0 {
		var counter = 0
		for {
			if ledMode == 0 {
				doLedToggling(counter, true)
			} else if ledMode == 1 {
				doLedToggling(counter, false)
				doLedToggling(counter + 1, false)
				doLedToggling(counter + 2, true)
			}
			counter++
		}
	} else {
		for i := 0; i < *num; i++ {
			doLedToggling(i, true)
		}
	}
}
<commit_msg>Using pullUp<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"periph.io/x/periph/conn/gpio"
	"periph.io/x/periph/conn/gpio/gpioreg"
	"periph.io/x/periph/host"
	"github.com/rs/cors"
)

var (
	mode     = 2
	demoMode = false
	isPushoverEnabled = false

	// LEDs
	ledRedPin    = 4
	ledYellowPin = 17
	ledGreenPin  = 27
	ledToColor   = map[int]string{}
	ledMap = map[int]gpio.PinIO{}
	ledMode 		 = 0

	// Buttons
	buttonPin 	= 22
	buttons 		= map[int]gpio.PinIO{}

	// GPIOs
	gpios = map[int]Gpio{}
	demoNum = 26

	// Pushover
	pushoverUser = ""
	pushoverToken = ""
	pushoverApi = "https://api.pushover.net:443/1/messages.json"
)

func getLEDString(color string) string {
	return "Toggle " + strings.ToUpper(color)
}

func setGpio(id int, name string, value int) {
	gpios[id] = Gpio{id, name, value}
}

func toggleLED(id int, pin gpio.PinIO, color string) {
	//fmt.Println(getLEDString(color))
	if !demoMode {
		//fmt.Println("Val to write", !pin.Read())
		pin.Out(!pin.Read())
		value := 0
		if pin.Read() == gpio.High {
			value = 1
		}
		setGpio(id, pin.Name(), value)
	}
}

func initButtons() {
	buttons[0] = gpioreg.ByName(strconv.Itoa(buttonPin))
	if err := buttons[0].In(gpio.PullUp, gpio.BothEdges); err != nil {
  	log.Fatal(err)
  }
	for buttonIndex := 0; buttonIndex < len(buttons); buttonIndex++ {
		fmt.Printf("%s: %s\n", buttons[buttonIndex], buttons[buttonIndex].Function())
	}
}

func initLEDs() {
	if mode == 2 {
		ledMap[0] = gpioreg.ByName(strconv.Itoa(ledRedPin))
		ledMap[1] = gpioreg.ByName(strconv.Itoa(ledYellowPin))
		ledMap[2] = gpioreg.ByName(strconv.Itoa(ledGreenPin))
		for i := 0; i < len(ledMap); i++ {
			fmt.Println("Resetting ", ledMap[i])
			if err := ledMap[i].Out(gpio.Low); err != nil {
        log.Fatal(err)
      }
		}
	}
	initLEDcolors()
}

func initLEDcolors() {
	ledToColor[0] = "red"
	ledToColor[1] = "yellow"
	ledToColor[2] = "green"
}

func initGPIO() error {
	_,err := host.Init()
	return err
}

func doLedToggling(i int, isSleepEnabled bool) {
	if !demoMode {
	  if mode == 2{
			toggleLED(i%3, ledMap[i%3], ledToColor[i%3])
		}
	} else {
		fmt.Println(getLEDString(ledToColor[i%3]))
		setGpio(i%demoNum, "GPIO" + strconv.Itoa(i%demoNum), i % 2)
	}

	if isSleepEnabled {
		time.Sleep(time.Second)
	}
}

type Gpio struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Value int    `json:"value"`
}

func getGpios(w http.ResponseWriter, r *http.Request) {
	gpiosToJson := make([]Gpio, 0, len(gpios))
	fmt.Printf("Transforming %d GPIOs\n", len(gpios))
	for  _, value := range gpios {
  	gpiosToJson = append(gpiosToJson, value)
	}
	fmt.Printf("Transformed into %d gpios\n", len(gpiosToJson))
	json, err := json.Marshal(gpiosToJson)

	if err != nil {
		panic(err)
	}
	w.Write(json)
}

type PushoverMessage struct {
	Token string `json:"token"`
	User string `json:"user"`
	Message string `json:"message"`
}

func toggleLedMode() {
	if ledMode == 0 {
		ledMode = 1
	} else {
		ledMode = 0
	}
}

func ledModeHandler(w http.ResponseWriter, r *http.Request) {
	toggleLedMode()

	if isPushoverEnabled {
		message := PushoverMessage{pushoverToken, pushoverUser, "LED mode toggled"}
		json, err := json.Marshal(message)
		var jsonStr = []byte(json)

		// Build the request
		client := &http.Client{}
		req, err := http.NewRequest("POST", pushoverApi, bytes.NewBuffer(jsonStr))
		if err != nil {
			fmt.Println("Request ERROR:", err)
			return
		}

	  req.Header.Add("Content-Type", "application/json")
		resp, err := client.Do(req)
		if err != nil {
			fmt.Println("Response ERROR:", err)
			return
		}
		defer resp.Body.Close()
		fmt.Println("Response: ", *resp)
	}
}

func initWebServer() {
	fmt.Println("Initializing Webserver")
	mux := http.NewServeMux()
	mux.HandleFunc("/v1/gpios", getGpios)
	mux.HandleFunc("/v1/ledMode", ledModeHandler)
	handler := cors.Default().Handler(mux)
	http.ListenAndServe(":8080", handler)
}

func listenForButtonPress(button gpio.PinIO) {
	buttonState := gpio.High
	for {
		fmt.Println("Check button ", button)
		button.WaitForEdge(-1)
		buttonState = button.Read()
		fmt.Printf("-> %s\n", buttonState)
		if buttonState == gpio.High {
			toggleLedMode()
		}
	}
}

func listenForButtonsPress() {
	fmt.Println("Listening for button presses")
	listenForButtonPress(buttons[0])
}

func main() {
	fmt.Println("Parsing parameters")
	num := flag.Int("num", 0, "number of blinks")
	buttonEnabled := flag.Bool("button", false, "button mode")
	api := flag.Bool("api", true, "API enabled")
	demo := flag.Bool("demo", false, "Demo mode enabled")
	pushoverFromCli := flag.Bool("pushover", false, "Pushover notifications enabled")

	flag.Parse()

	demoMode = *demo
	isPushoverEnabled = *pushoverFromCli
	pushoverUser = os.Getenv("PUSHOVER_USER")
	pushoverToken = os.Getenv("PUSHOVER_TOKEN")

	if isPushoverEnabled && (pushoverUser == "" || pushoverToken == "") {
		fmt.Println("Pushover env variables are undefined, disabling pushover notifs")
		isPushoverEnabled = false
	}

	if demoMode {
		fmt.Println("Running in demo mode, no physical hw interaction")
	}

	fmt.Println("Number of blinks:", *num)

	if *api {
		go initWebServer()
	}

	if !demoMode {
		var err = initGPIO()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		initLEDs()
		initButtons()
		if *buttonEnabled {
			go listenForButtonsPress()
		}
	} else {
		initLEDcolors()
	}

	if *num == 0 {
		var counter = 0
		for {
			if ledMode == 0 {
				doLedToggling(counter, true)
			} else if ledMode == 1 {
				doLedToggling(counter, false)
				doLedToggling(counter + 1, false)
				doLedToggling(counter + 2, true)
			}
			counter++
		}
	} else {
		for i := 0; i < *num; i++ {
			doLedToggling(i, true)
		}
	}
}
<|endoftext|>


<commit_before>/* Copyright 2016 Google Inc.

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 gonids

import (
	"errors"
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"
)

// item represents a token or text string returned from the lexer.
type item struct {
	typ   itemType // The type of this item.
	value string   // The value of this item.
}

// String returns a string describing an item.
func (i item) String() string {
	switch i.typ {
	case itemEOF:
		return "EOF"
	case itemError:
		return i.value
	}
	return fmt.Sprintf("%q: %s", i.typ, i.value)
}

type itemType int

const (
	itemError itemType = iota
	itemComment
	itemAction
	itemProtocol
	itemSourceAddress
	itemSourcePort
	itemDirection
	itemDestinationAddress
	itemDestinationPort
	itemNot
	itemOptionKey
	itemOptionValue
	itemOptionNoValue
	itemOptionValueString
	itemEOR
	itemEOF
)

const eof = -1

// stateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*lexer) stateFn

// lexer holds the state of the scanner.
type lexer struct {
	input string    // the string being scanned
	state stateFn   // the next lexing function to enter
	pos   int       // current position in the input
	start int       // start position of this item
	width int       // width of last rune read from input
	items chan item // channel of scanned items
}

// next returns the next rune in the input.
func (l *lexer) next() rune {
	if l.pos >= len(l.input) {
		l.width = 0
		return eof
	}
	r, w := utf8.DecodeRuneInString(l.input[l.pos:])
	if r == utf8.RuneError && w == 1 {
		// The whole input string has been validated at init.
		panic("invalid UTF-8 character")
	}
	l.width = w
	l.pos += l.width
	return r
}

// skipNext skips over the next rune in the input.
func (l *lexer) skipNext() {
	l.next()
	l.ignore()
}

// peek returns but does not consume the next rune in the input.
func (l *lexer) peek() rune {
	r := l.next()
	l.backup()
	return r
}

// len returns the current length of the item in processing.
func (l *lexer) len() int {
	if l.pos >= len(l.input) {
		return -1
	}
	return l.pos - l.start
}

// backup steps back one rune. Can only be called once per call of next.
func (l *lexer) backup() {
	if l.width == -1 {
		panic("double backup")
	}
	l.pos -= l.width
	l.width = -1
}

// emit passes an item back to the client, trimSpaces can be used to trim spaces around item
// value before emiting.
func (l *lexer) emit(t itemType, trimSpaces bool) {
	input := l.input[l.start:l.pos]
	if trimSpaces {
		input = strings.TrimSpace(input)
	}

	// This is a bit of a hack. We lex until `;` now so we end up with extra `"`.
	input = strings.TrimSuffix(input, `"`)
	l.items <- item{t, input}
	l.start = l.pos
}

// ignore skips over the pending input before this point.
func (l *lexer) ignore() {
	l.start = l.pos
}

// accept consumes the next rune if it's from the valid set.
func (l *lexer) accept(valid string) bool {
	if strings.IndexRune(valid, l.next()) >= 0 {
		return true
	}
	l.backup()
	return false
}

// acceptRun consumes a run of runes from the valid set.
func (l *lexer) acceptRun(valid string) {
	for strings.IndexRune(valid, l.next()) >= 0 {
	}
	l.backup()
}

// ignoreSpaces ignores all spaces at the start of the input.
func (l *lexer) ignoreSpaces() {
	for unicode.IsSpace(l.next()) {
		l.ignore()
	}
	l.backup()
}

// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	l.items <- item{itemError, fmt.Sprintf(format, args...)}
	return nil
}

func (l *lexer) unexpectedEOF() stateFn {
	close(l.items)
	return nil
}

// nextItem returns the next item from the input.
func (l *lexer) nextItem() item {
	r, more := <-l.items
	if !more {
		return item{itemError, "unexpected EOF"}
	}
	return r
}

// lex initializes and runs a new scanner for the input string.
func lex(input string) (*lexer, error) {
	if !utf8.ValidString(input) {
		return nil, errors.New("input is not a valid UTF-8 string")
	}
	l := &lexer{
		input: input,
		items: make(chan item),
	}
	go l.run()
	return l, nil
}

// TODO: handle error and corner case in all states.
// run runs the state machine for the lexer.
func (l *lexer) run() {
	for l.state = lexRule; l.state != nil; {
		l.state = l.state(l)
	}
}

// lexRule starts the scan of a rule.
func lexRule(l *lexer) stateFn {
	r := l.next()
	switch {
	case unicode.IsSpace(r):
		l.ignore()
		return lexRule
	case r == '#':
		return lexComment
	case r == eof:
		l.emit(itemEOF, false)
		return nil
	}
	return lexAction
}

// lexComment consumes a commented rule.
func lexComment(l *lexer) stateFn {
	// Ignore leading spaces and #.
	l.ignore()
	for {
		r := l.next()
		if unicode.IsSpace(r) || r == '#' {
			l.ignore()
		} else {
			break
		}
	}
	l.backup()

	for {
		switch l.next() {
		case '\r', '\n':
			l.emit(itemComment, false)
			return lexRule
		case eof:
			l.backup()
			l.emit(itemComment, false)
			return lexRule
		}
	}
}

// lexAction consumes a rule action.
func lexAction(l *lexer) stateFn {
	for {
		r := l.next()
		switch {
		case r == ' ':
			l.emit(itemAction, true)
			return lexProtocol
		case !unicode.IsLetter(r):
			return l.errorf("invalid character %q for a rule action", r)
		}
	}
}

// lexProtocol consumes a rule protocol.
func lexProtocol(l *lexer) stateFn {
	l.ignoreSpaces()
	for {
		r := l.next()
		switch {
		case r == ' ':
			l.emit(itemProtocol, true)
			return lexSourceAddress
		case !(unicode.IsLetter(r) || (l.len() > 0 && r == '-')):
			return l.errorf("invalid character %q for a rule protocol", r)
		}
	}

}

// lexSourceAddress consumes a source address.
func lexSourceAddress(l *lexer) stateFn {
	l.ignoreSpaces()
	for {
		switch l.next() {
		case ' ':
			l.emit(itemSourceAddress, true)
			return lexSourcePort
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexSourcePort consumes a source port.
func lexSourcePort(l *lexer) stateFn {
	l.ignoreSpaces()
	for {
		switch l.next() {
		case ' ':
			l.emit(itemSourcePort, true)
			return lexDirection
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexDirection consumes a rule direction.
func lexDirection(l *lexer) stateFn {
	l.ignoreSpaces()
	l.acceptRun("<->")
	if r := l.next(); r != ' ' {
		return l.errorf("invalid character %q for a rule direction", r)
	}
	l.emit(itemDirection, true)
	return lexDestinationAddress
}

// lexDestinationAddress consumes a destination address.
func lexDestinationAddress(l *lexer) stateFn {
	l.ignoreSpaces()
	for {
		switch l.next() {
		case ' ':
			l.emit(itemDestinationAddress, true)
			return lexDestinationPort
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexDestinationPort consumes a destination port.
func lexDestinationPort(l *lexer) stateFn {
	for {
		switch l.next() {
		case '(':
			l.backup()
			l.emit(itemDestinationPort, true)
			l.skipNext()
			return lexOptionKey
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexOptionKey scans a key from the rule options.
func lexOptionKey(l *lexer) stateFn {
	for {
		switch l.next() {
		case ':':
			l.backup()
			l.emit(itemOptionKey, true)
			l.skipNext()
			return lexOptionValueBegin
		case ';':
			l.backup()
			if l.pos > l.start {
				l.emit(itemOptionKey, true)
				l.emit(itemOptionNoValue, true)
			}
			l.skipNext()
			return lexOptionKey
		case ')':
			l.backup()
			if l.pos > l.start {
				l.emit(itemOptionKey, true)
			}
			l.skipNext()
			return lexRuleEnd
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexOptionValueBegin scans the beginning of a value from the rule option.
func lexOptionValueBegin(l *lexer) stateFn {
	switch l.next() {
	case '"':
		l.ignore()
		return lexOptionValueString
	case ' ':
		l.ignore()
		return lexOptionValueBegin
	case '!':
		l.emit(itemNot, true)
		return lexOptionValueBegin
	}
	return lexOptionValue
}

// lexOptionValueString consumes the inner content of a string value from the rule options.
func lexOptionValueString(l *lexer) stateFn {
	escaped := false
	for {
		switch l.next() {
		case ';':
			l.backup()
			l.emit(itemOptionValueString, false)
			l.skipNext()
			return lexOptionKey
		case '\\':
			escaped = !escaped
			if l.next() != ';' || !escaped {
				l.backup()
			}
		case eof:
			return l.unexpectedEOF()
		default:
			escaped = false
		}
	}
}

// lexOptionValue scans a value from the rule options.
func lexOptionValue(l *lexer) stateFn {
	for {
		switch l.next() {
		case ';':
			l.backup()
			l.emit(itemOptionValue, true)
			l.skipNext()
			return lexOptionKey
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexOptionEnd marks the end of a rule.
func lexRuleEnd(l *lexer) stateFn {
	l.acceptRun(" \t;")
	l.ignore()
	l.emit(itemEOR, false)
	return lexRule
}
<commit_msg>Fix lexRuleEnd. If we hit this we should just stop.<commit_after>/* Copyright 2016 Google Inc.

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 gonids

import (
	"errors"
	"fmt"
	"strings"
	"unicode"
	"unicode/utf8"
)

// item represents a token or text string returned from the lexer.
type item struct {
	typ   itemType // The type of this item.
	value string   // The value of this item.
}

// String returns a string describing an item.
func (i item) String() string {
	switch i.typ {
	case itemEOF:
		return "EOF"
	case itemError:
		return i.value
	}
	return fmt.Sprintf("%q: %s", i.typ, i.value)
}

type itemType int

const (
	itemError itemType = iota
	itemComment
	itemAction
	itemProtocol
	itemSourceAddress
	itemSourcePort
	itemDirection
	itemDestinationAddress
	itemDestinationPort
	itemNot
	itemOptionKey
	itemOptionValue
	itemOptionNoValue
	itemOptionValueString
	itemEOR
	itemEOF
)

const eof = -1

// stateFn represents the state of the scanner as a function that returns the next state.
type stateFn func(*lexer) stateFn

// lexer holds the state of the scanner.
type lexer struct {
	input string    // the string being scanned
	state stateFn   // the next lexing function to enter
	pos   int       // current position in the input
	start int       // start position of this item
	width int       // width of last rune read from input
	items chan item // channel of scanned items
}

// next returns the next rune in the input.
func (l *lexer) next() rune {
	if l.pos >= len(l.input) {
		l.width = 0
		return eof
	}
	r, w := utf8.DecodeRuneInString(l.input[l.pos:])
	if r == utf8.RuneError && w == 1 {
		// The whole input string has been validated at init.
		panic("invalid UTF-8 character")
	}
	l.width = w
	l.pos += l.width
	return r
}

// skipNext skips over the next rune in the input.
func (l *lexer) skipNext() {
	l.next()
	l.ignore()
}

// peek returns but does not consume the next rune in the input.
func (l *lexer) peek() rune {
	r := l.next()
	l.backup()
	return r
}

// len returns the current length of the item in processing.
func (l *lexer) len() int {
	if l.pos >= len(l.input) {
		return -1
	}
	return l.pos - l.start
}

// backup steps back one rune. Can only be called once per call of next.
func (l *lexer) backup() {
	if l.width == -1 {
		panic("double backup")
	}
	l.pos -= l.width
	l.width = -1
}

// emit passes an item back to the client, trimSpaces can be used to trim spaces around item
// value before emiting.
func (l *lexer) emit(t itemType, trimSpaces bool) {
	input := l.input[l.start:l.pos]
	if trimSpaces {
		input = strings.TrimSpace(input)
	}

	// This is a bit of a hack. We lex until `;` now so we end up with extra `"`.
	input = strings.TrimSuffix(input, `"`)
	l.items <- item{t, input}
	l.start = l.pos
}

// ignore skips over the pending input before this point.
func (l *lexer) ignore() {
	l.start = l.pos
}

// accept consumes the next rune if it's from the valid set.
func (l *lexer) accept(valid string) bool {
	if strings.IndexRune(valid, l.next()) >= 0 {
		return true
	}
	l.backup()
	return false
}

// acceptRun consumes a run of runes from the valid set.
func (l *lexer) acceptRun(valid string) {
	for strings.IndexRune(valid, l.next()) >= 0 {
	}
	l.backup()
}

// ignoreSpaces ignores all spaces at the start of the input.
func (l *lexer) ignoreSpaces() {
	for unicode.IsSpace(l.next()) {
		l.ignore()
	}
	l.backup()
}

// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
	l.items <- item{itemError, fmt.Sprintf(format, args...)}
	return nil
}

func (l *lexer) unexpectedEOF() stateFn {
	close(l.items)
	return nil
}

// nextItem returns the next item from the input.
func (l *lexer) nextItem() item {
	r, more := <-l.items
	if !more {
		return item{itemError, "unexpected EOF"}
	}
	return r
}

// lex initializes and runs a new scanner for the input string.
func lex(input string) (*lexer, error) {
	if !utf8.ValidString(input) {
		return nil, errors.New("input is not a valid UTF-8 string")
	}
	l := &lexer{
		input: input,
		items: make(chan item),
	}
	go l.run()
	return l, nil
}

// TODO: handle error and corner case in all states.
// run runs the state machine for the lexer.
func (l *lexer) run() {
	for l.state = lexRule; l.state != nil; {
		l.state = l.state(l)
	}
}

// lexRule starts the scan of a rule.
func lexRule(l *lexer) stateFn {
	r := l.next()
	switch {
	case unicode.IsSpace(r):
		l.ignore()
		return lexRule
	case r == '#':
		return lexComment
	case r == eof:
		l.emit(itemEOF, false)
		return nil
	}
	return lexAction
}

// lexComment consumes a commented rule.
func lexComment(l *lexer) stateFn {
	// Ignore leading spaces and #.
	l.ignore()
	for {
		r := l.next()
		if unicode.IsSpace(r) || r == '#' {
			l.ignore()
		} else {
			break
		}
	}
	l.backup()

	for {
		switch l.next() {
		case '\r', '\n':
			l.emit(itemComment, false)
			return lexRule
		case eof:
			l.backup()
			l.emit(itemComment, false)
			return lexRule
		}
	}
}

// lexAction consumes a rule action.
func lexAction(l *lexer) stateFn {
	for {
		r := l.next()
		switch {
		case r == ' ':
			l.emit(itemAction, true)
			return lexProtocol
		case !unicode.IsLetter(r):
			return l.errorf("invalid character %q for a rule action", r)
		}
	}
}

// lexProtocol consumes a rule protocol.
func lexProtocol(l *lexer) stateFn {
	l.ignoreSpaces()
	for {
		r := l.next()
		switch {
		case r == ' ':
			l.emit(itemProtocol, true)
			return lexSourceAddress
		case !(unicode.IsLetter(r) || (l.len() > 0 && r == '-')):
			return l.errorf("invalid character %q for a rule protocol", r)
		}
	}

}

// lexSourceAddress consumes a source address.
func lexSourceAddress(l *lexer) stateFn {
	l.ignoreSpaces()
	for {
		switch l.next() {
		case ' ':
			l.emit(itemSourceAddress, true)
			return lexSourcePort
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexSourcePort consumes a source port.
func lexSourcePort(l *lexer) stateFn {
	l.ignoreSpaces()
	for {
		switch l.next() {
		case ' ':
			l.emit(itemSourcePort, true)
			return lexDirection
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexDirection consumes a rule direction.
func lexDirection(l *lexer) stateFn {
	l.ignoreSpaces()
	l.acceptRun("<->")
	if r := l.next(); r != ' ' {
		return l.errorf("invalid character %q for a rule direction", r)
	}
	l.emit(itemDirection, true)
	return lexDestinationAddress
}

// lexDestinationAddress consumes a destination address.
func lexDestinationAddress(l *lexer) stateFn {
	l.ignoreSpaces()
	for {
		switch l.next() {
		case ' ':
			l.emit(itemDestinationAddress, true)
			return lexDestinationPort
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexDestinationPort consumes a destination port.
func lexDestinationPort(l *lexer) stateFn {
	for {
		switch l.next() {
		case '(':
			l.backup()
			l.emit(itemDestinationPort, true)
			l.skipNext()
			return lexOptionKey
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexOptionKey scans a key from the rule options.
func lexOptionKey(l *lexer) stateFn {
	for {
		switch l.next() {
		case ':':
			l.backup()
			l.emit(itemOptionKey, true)
			l.skipNext()
			return lexOptionValueBegin
		case ';':
			l.backup()
			if l.pos > l.start {
				l.emit(itemOptionKey, true)
				l.emit(itemOptionNoValue, true)
			}
			l.skipNext()
			return lexOptionKey
		case ')':
			l.backup()
			if l.pos > l.start {
				l.emit(itemOptionKey, true)
			}
			l.skipNext()
			return lexRuleEnd
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexOptionValueBegin scans the beginning of a value from the rule option.
func lexOptionValueBegin(l *lexer) stateFn {
	switch l.next() {
	case '"':
		l.ignore()
		return lexOptionValueString
	case ' ':
		l.ignore()
		return lexOptionValueBegin
	case '!':
		l.emit(itemNot, true)
		return lexOptionValueBegin
	}
	return lexOptionValue
}

// lexOptionValueString consumes the inner content of a string value from the rule options.
func lexOptionValueString(l *lexer) stateFn {
	escaped := false
	for {
		switch l.next() {
		case ';':
			l.backup()
			l.emit(itemOptionValueString, false)
			l.skipNext()
			return lexOptionKey
		case '\\':
			escaped = !escaped
			if l.next() != ';' || !escaped {
				l.backup()
			}
		case eof:
			return l.unexpectedEOF()
		default:
			escaped = false
		}
	}
}

// lexOptionValue scans a value from the rule options.
func lexOptionValue(l *lexer) stateFn {
	for {
		switch l.next() {
		case ';':
			l.backup()
			l.emit(itemOptionValue, true)
			l.skipNext()
			return lexOptionKey
		case eof:
			return l.unexpectedEOF()
		}
	}
}

// lexOptionEnd marks the end of a rule.
func lexRuleEnd(l *lexer) stateFn {
	l.emit(itemEOR, false)
	return lexRule
}
<|endoftext|>


<commit_before>package helpers_test

import (
	"errors"
	"time"

	"github.com/cloudfoundry-incubator/routing-api/db"
	fake_db "github.com/cloudfoundry-incubator/routing-api/db/fakes"
	"github.com/cloudfoundry-incubator/routing-api/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pivotal-golang/lager/lagertest"
)

var _ = Describe("Helpers", func() {
	Describe("RegisterRoutingAPI", func() {
		var (
			database fake_db.FakeDB
			route    db.Route
			logger   *lagertest.TestLogger

			timeChan chan time.Time
			ticker   *time.Ticker

			quitChan chan bool
		)

		BeforeEach(func() {
			route = db.Route{
				Route:   "i dont care",
				Port:    3000,
				IP:      "i dont care even more",
				TTL:     120,
				LogGuid: "i care a little bit more now",
			}
			database = fake_db.FakeDB{}
			logger = lagertest.NewTestLogger("event-handler-test")

			timeChan = make(chan time.Time)
			ticker = &time.Ticker{C: timeChan}

			quitChan = make(chan bool)
		})

		Context("registration", func() {
			Context("with no errors", func() {
				BeforeEach(func() {
					database.SaveRouteStub = func(route db.Route) error {
						return nil
					}
				})

				It("registers the route for a routing api on init", func() {
					go helpers.RegisterRoutingAPI(quitChan, &database, route, ticker, logger)

					Eventually(func() int { return database.SaveRouteCallCount() }).Should(Equal(1))
					Eventually(func() db.Route { return database.SaveRouteArgsForCall(0) }).Should(Equal(route))
				})

				It("registers on an interval", func() {
					go helpers.RegisterRoutingAPI(quitChan, &database, route, ticker, logger)
					timeChan <- time.Now()

					Eventually(func() int { return database.SaveRouteCallCount() }).Should(Equal(2))
					Eventually(func() db.Route { return database.SaveRouteArgsForCall(1) }).Should(Equal(route))
					Eventually(func() int { return len(logger.Logs()) }).Should(Equal(0))
				})
			})

			Context("when there are errors", func() {
				It("only logs the error once for each attempt", func() {
					database.SaveRouteStub = func(route db.Route) error {
						return errors.New("beep boop, self destruct mode engaged")
					}

					go helpers.RegisterRoutingAPI(quitChan, &database, route, ticker, logger)

					Consistently(func() int { return len(logger.Logs()) }).Should(BeNumerically("<=", 1))
					Eventually(func() string {
						if len(logger.Logs()) > 0 {
							return logger.Logs()[0].Data["error"].(string)
						} else {
							return ""
						}
					}).Should(ContainSubstring("beep boop, self destruct mode engaged"))
				})
			})
		})

		Context("unregistration", func() {
			It("unregisters the routing api when a quit message is received", func() {
				go func() {
					quitChan <- true
				}()

				helpers.RegisterRoutingAPI(quitChan, &database, route, ticker, logger)

				Expect(database.DeleteRouteCallCount()).To(Equal(1))
				Expect(database.DeleteRouteArgsForCall(0)).To(Equal(route))
				Expect(quitChan).To(BeClosed())
			})
		})
	})
})
<commit_msg>Remove unnecessary func wrappers in Eventually.<commit_after>package helpers_test

import (
	"errors"
	"time"

	"github.com/cloudfoundry-incubator/routing-api/db"
	fake_db "github.com/cloudfoundry-incubator/routing-api/db/fakes"
	"github.com/cloudfoundry-incubator/routing-api/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pivotal-golang/lager/lagertest"
)

var _ = Describe("Helpers", func() {
	Describe("RegisterRoutingAPI", func() {
		var (
			database fake_db.FakeDB
			route    db.Route
			logger   *lagertest.TestLogger

			timeChan chan time.Time
			ticker   *time.Ticker

			quitChan chan bool
		)

		BeforeEach(func() {
			route = db.Route{
				Route:   "i dont care",
				Port:    3000,
				IP:      "i dont care even more",
				TTL:     120,
				LogGuid: "i care a little bit more now",
			}
			database = fake_db.FakeDB{}
			logger = lagertest.NewTestLogger("event-handler-test")

			timeChan = make(chan time.Time)
			ticker = &time.Ticker{C: timeChan}

			quitChan = make(chan bool)
		})

		Context("registration", func() {
			Context("with no errors", func() {
				BeforeEach(func() {
					database.SaveRouteStub = func(route db.Route) error {
						return nil
					}
				})

				It("registers the route for a routing api on init", func() {
					go helpers.RegisterRoutingAPI(quitChan, &database, route, ticker, logger)

					Eventually(database.SaveRouteCallCount).Should(Equal(1))
					Eventually(func() db.Route { return database.SaveRouteArgsForCall(0) }).Should(Equal(route))
				})

				It("registers on an interval", func() {
					go helpers.RegisterRoutingAPI(quitChan, &database, route, ticker, logger)
					timeChan <- time.Now()

					Eventually(database.SaveRouteCallCount).Should(Equal(2))
					Eventually(func() db.Route { return database.SaveRouteArgsForCall(1) }).Should(Equal(route))
					Eventually(logger.Logs).Should(HaveLen(0))
				})
			})

			Context("when there are errors", func() {
				It("only logs the error once for each attempt", func() {
					database.SaveRouteStub = func(route db.Route) error {
						return errors.New("beep boop, self destruct mode engaged")
					}

					go helpers.RegisterRoutingAPI(quitChan, &database, route, ticker, logger)

					Consistently(func() int { return len(logger.Logs()) }).Should(BeNumerically("<=", 1))
					Eventually(func() string {
						if len(logger.Logs()) > 0 {
							return logger.Logs()[0].Data["error"].(string)
						} else {
							return ""
						}
					}).Should(ContainSubstring("beep boop, self destruct mode engaged"))
				})
			})
		})

		Context("unregistration", func() {
			It("unregisters the routing api when a quit message is received", func() {
				go func() {
					quitChan <- true
				}()

				helpers.RegisterRoutingAPI(quitChan, &database, route, ticker, logger)

				Expect(database.DeleteRouteCallCount()).To(Equal(1))
				Expect(database.DeleteRouteArgsForCall(0)).To(Equal(route))
				Expect(quitChan).To(BeClosed())
			})
		})
	})
})
<|endoftext|>


<commit_before>package job

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"os/exec"
	"strings"
	"time"

	"github.com/austinkelmore/catarang/multilog"
)

// Status The job instance's status
type Status int

const (
	// RUNNING The job instance is currently running
	RUNNING Status = iota
	// FAILED The job instance failed
	FAILED
	// SUCCESSFUL The job instance was successful
	SUCCESSFUL
)

// Instance a single run of a job
type Instance struct {
	StartTime time.Time
	EndTime   time.Time
	Config    Config
	// todo: akelmore - make the build command more robust than a string
	BuildCommand BuildCommand
	Status       Status
	// todo: akelmore - move the log from the instance to somewhere else?
	Log []multilog.Log
}

// NewInstance Creates a new instance of a job (and copies off the current config)
// and starts the instance running
func NewInstance(config Config) Instance {
	inst := Instance{StartTime: time.Now(), Status: RUNNING, Config: config}
	return inst
}

func (i *Instance) appendLog(name string) *multilog.Log {
	i.Log = append(i.Log, multilog.New(name))
	return &i.Log[len(i.Log)-1]
}

func (i *Instance) fail(reason string) {
	// create a log to the buffer so we can write to it
	logger := log.New(&i.Log[len(i.Log)-1].Err, "", 0)
	logger.Println(reason)
	i.Status = FAILED
}

// todo: akelmore - do i still need to update the build command before starting every time? should i only do it sometimes or not at all?
func (i *Instance) updateBuildCommand() error {
	// read in the config file's build command
	file, err := ioutil.ReadFile(i.Config.SourceControl.LocalRepoPath() + i.Config.BuildConfigPath)
	if err != nil {
		log.Println("Error reading build config file: " + i.Config.BuildConfigPath)
		return err
	}
	err = json.Unmarshal(file, &i.BuildCommand)
	if err != nil {
		log.Println("Error reading JSON from build config file: " + i.Config.BuildConfigPath)
		return err
	}

	return nil
}

// Start Entry point for the instance
func (i *Instance) Start(completedSetup *bool) {
	// todo: akelmore - make jobs have an array of "things" to do rather than hard code scm stuff first
	// todo: akelmore - pull out the notion of a first time setup and let modules have their own internal states on a per-job basis
	if !*completedSetup {
		logger := i.appendLog("git - initial setup")
		if err := i.Config.SourceControl.FirstTimeSetup(logger); err != nil {
			i.fail(err.Error())
			return
		}
		*completedSetup = true
	} else {
		logger := i.appendLog("git - sync")
		if err := i.Config.SourceControl.UpdateExisting(logger); err != nil {
			i.fail(err.Error())
			return
		}
	}

	logger := i.appendLog("cmd")
	if err := i.updateBuildCommand(); err != nil {
		log.Println("Error updating build command from config file.")
		return
	}

	fields := strings.Fields(i.BuildCommand.ExecCommand)
	if len(fields) > 0 {
		cmd := exec.Command(fields[0], fields[1:]...)
		cmd.Stdout = &logger.Out
		cmd.Stderr = &logger.Err
		cmd.Dir = i.Config.SourceControl.LocalRepoPath()
		if err := cmd.Run(); err != nil {
			i.fail("Error running exec command.")
			return
		}
	}

	log.Println("Success running exec command!")
	i.Status = SUCCESSFUL
}
<commit_msg>Fixing up build so it runs and making it auto-look for .catarang.json file if a build config is not specified.<commit_after>package job

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"os/exec"
	"strings"
	"time"

	"github.com/austinkelmore/catarang/multilog"
)

// Status The job instance's status
type Status int

const (
	// RUNNING The job instance is currently running
	RUNNING Status = iota
	// FAILED The job instance failed
	FAILED
	// SUCCESSFUL The job instance was successful
	SUCCESSFUL
)

// Instance a single run of a job
type Instance struct {
	StartTime time.Time
	EndTime   time.Time
	Config    Config
	// todo: akelmore - make the build command more robust than a string
	BuildCommand BuildCommand
	Status       Status
	// todo: akelmore - move the log from the instance to somewhere else?
	Log []multilog.Log
}

// NewInstance Creates a new instance of a job (and copies off the current config)
// and starts the instance running
func NewInstance(config Config) Instance {
	inst := Instance{StartTime: time.Now(), Status: RUNNING, Config: config}
	return inst
}

func (i *Instance) appendLog(name string) *multilog.Log {
	i.Log = append(i.Log, multilog.New(name))
	return &i.Log[len(i.Log)-1]
}

func (i *Instance) fail(reason string) {
	// create a log to the buffer so we can write to it
	logger := log.New(&i.Log[len(i.Log)-1].Err, "", 0)
	logger.Println(reason)
	i.Status = FAILED
}

// todo: akelmore - do i still need to update the build command before starting every time? should i only do it sometimes or not at all?
func (i *Instance) updateBuildCommand() error {
	// read in the config file's build command
	path := i.Config.BuildConfigPath
	if path == "" {
		path = ".catarang.json"
	}

	file, err := ioutil.ReadFile(i.Config.SourceControl.LocalRepoPath() + path)
	if err != nil {
		return errors.New("Error reading build config file: " + path)
	}
	err = json.Unmarshal(file, &i.BuildCommand)
	if err != nil {
		return errors.New("Error reading JSON from build config file: " + path)
	}

	return nil
}

// Start Entry point for the instance
// todo: akelmore - i don't like passing a bool for the completedSetup, figure something better out
func (i *Instance) Start(completedSetup *bool) {
	// todo: akelmore - make jobs have an array of "things" to do rather than hard code scm stuff first
	// todo: akelmore - pull out the notion of a first time setup and let modules have their own internal states on a per-job basis
	if !*completedSetup {
		logger := i.appendLog("git - initial setup")
		if err := i.Config.SourceControl.FirstTimeSetup(logger); err != nil {
			i.fail(err.Error())
			return
		}
		*completedSetup = true
	} else {
		logger := i.appendLog("git - sync")
		if err := i.Config.SourceControl.UpdateExisting(logger); err != nil {
			i.fail(err.Error())
			return
		}
	}

	logger := i.appendLog("cmd")
	if err := i.updateBuildCommand(); err != nil {
		i.fail("Error updating build command from config file.")
		return
	}

	fields := strings.Fields(i.BuildCommand.ExecCommand)
	if len(fields) > 0 {
		cmd := exec.Command(fields[0], fields[1:]...)
		cmd.Stdout = &logger.Out
		cmd.Stderr = &logger.Err
		cmd.Dir = i.Config.SourceControl.LocalRepoPath()
		if err := cmd.Run(); err != nil {
			i.fail("Error running exec command.")
			return
		}
	}

	log.Println("Success running exec command!")
	i.Status = SUCCESSFUL
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
)

var version int = 1

var (
	flPort    int
	flDir     string
	flSignKey string
)

var workingDir string

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	flag.IntVar(&flPort, "port", 8888, "server port")
	flag.StringVar(&flDir, "dir", os.TempDir(), "building dir")
	flag.StringVar(&flSignKey, "key", "", "Sign GPG key")
}

func initWorkingDir() {
	_, err := os.Stat(workingDir)
	if err == nil || (err != nil && os.IsExist(err)) {
		// WorkingDir exists
		if err := os.RemoveAll(workingDir); err != nil {
			panic("Remove working dir error")
		}
		goto create
	}
	if err != nil && os.IsNotExist(err) {
		goto create
	} else {
		panic("stat working dir error")
	}

create:
	if err := os.MkdirAll(workingDir, 0755); err != nil {
		panic("mkdir working dir error")
	}

}

func main() {

	flag.Parse()

	if flSignKey == "" {
		flag.Usage()
	}

	workingDir = filepath.Join(flDir, "BuildDog", strconv.Itoa(version))

	initWorkingDir()
	initTaskPool()

	serv := newAPIServer()
	panic(serv.ListenAndServe())
}
<commit_msg>change default port from 8888 to 8808<commit_after>package main

import (
	"flag"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
)

var version int = 1

var (
	flPort    int
	flDir     string
	flSignKey string
)

var workingDir string

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	flag.IntVar(&flPort, "port", 8808, "server port")
	flag.StringVar(&flDir, "dir", os.TempDir(), "building dir")
	flag.StringVar(&flSignKey, "key", "", "Sign GPG key")
}

func initWorkingDir() {
	_, err := os.Stat(workingDir)
	if err == nil || (err != nil && os.IsExist(err)) {
		// WorkingDir exists
		if err := os.RemoveAll(workingDir); err != nil {
			panic("Remove working dir error")
		}
		goto create
	}
	if err != nil && os.IsNotExist(err) {
		goto create
	} else {
		panic("stat working dir error")
	}

create:
	if err := os.MkdirAll(workingDir, 0755); err != nil {
		panic("mkdir working dir error")
	}

}

func main() {

	flag.Parse()

	if flSignKey == "" {
		flag.Usage()
	}

	workingDir = filepath.Join(flDir, "BuildDog", strconv.Itoa(version))

	initWorkingDir()
	initTaskPool()

	serv := newAPIServer()
	panic(serv.ListenAndServe())
}
<|endoftext|>


<commit_before>// +build e2e

/*
Copyright 2018 Knative Authors LLC
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 test

import (
	"fmt"
	"testing"

	knativetest "github.com/knative/pkg/test"
	"github.com/knative/pkg/test/logging"
	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
	tb "github.com/tektoncd/pipeline/test/builder"
)

const (
	embedTaskName    = "helloworld"
	embedTaskRunName = "helloworld-run"

	// TODO(#127) Currently not reliable to retrieve this output
	taskOutput = "do you want to build a snowman"
)

func getEmbeddedTask(namespace string, args []string) *v1alpha1.Task {
	return tb.Task(embedTaskName, namespace,
		tb.TaskSpec(
			tb.TaskInputs(tb.InputsResource("docs", v1alpha1.PipelineResourceTypeGit)),
			tb.Step("read", "ubuntu",
				tb.Command("/bin/bash"),
				tb.Args("-c", "cat /workspace/docs/README.md"),
			),
			tb.Step("helloworld-busybox", "busybox", tb.Command(args...)),
		))
}

func getEmbeddedTaskRun(namespace string) *v1alpha1.TaskRun {
	testSpec := &v1alpha1.PipelineResourceSpec{
		Type: v1alpha1.PipelineResourceTypeGit,
		Params: []v1alpha1.Param{{
			Name:  "URL",
			Value: "http://github.com/knative/docs",
		}},
	}
	return tb.TaskRun(embedTaskRunName, namespace,
		tb.TaskRunSpec(
			tb.TaskRunInputs(
				tb.TaskRunInputsResource("docs", tb.TaskResourceBindingResourceSpec(testSpec)),
			),
			tb.TaskRunTaskRef(embedTaskName)))
}

// TestTaskRun_EmbeddedResource is an integration test that will verify a very simple "hello world" TaskRun can be
// executed with an embedded resource spec.
func TestTaskRun_EmbeddedResource(t *testing.T) {
	logger := logging.GetContextLogger(t.Name())
	c, namespace := setup(t, logger)
	t.Parallel()

	knativetest.CleanupOnInterrupt(func() { tearDown(t, logger, c, namespace) }, logger)
	defer tearDown(t, logger, c, namespace)

	logger.Infof("Creating Task and TaskRun in namespace %s", namespace)
	if _, err := c.TaskClient.Create(getEmbeddedTask(namespace, []string{"/bin/sh", "-c", fmt.Sprintf("echo %s", taskOutput)})); err != nil {
		t.Fatalf("Failed to create Task `%s`: %s", embedTaskName, err)
	}
	if _, err := c.TaskRunClient.Create(getEmbeddedTaskRun(namespace)); err != nil {
		t.Fatalf("Failed to create TaskRun `%s`: %s", embedTaskRunName, err)
	}

	logger.Infof("Waiting for TaskRun %s in namespace %s to complete", embedTaskRunName, namespace)
	if err := WaitForTaskRunState(c, embedTaskRunName, TaskRunSucceed(embedTaskRunName), "TaskRunSuccess"); err != nil {
		t.Errorf("Error waiting for TaskRun %s to finish: %s", embedTaskRunName, err)
	}

	// TODO(#127) Currently we have no reliable access to logs from the TaskRun so we'll assume successful
	// completion of the TaskRun means the TaskRun did what it was intended.
}
<commit_msg>Fix embed_test.go : there is no README.md in knative/docs anymore…<commit_after>// +build e2e

/*
Copyright 2018 Knative Authors LLC
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 test

import (
	"fmt"
	"testing"

	knativetest "github.com/knative/pkg/test"
	"github.com/knative/pkg/test/logging"
	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
	tb "github.com/tektoncd/pipeline/test/builder"
)

const (
	embedTaskName    = "helloworld"
	embedTaskRunName = "helloworld-run"

	// TODO(#127) Currently not reliable to retrieve this output
	taskOutput = "do you want to build a snowman"
)

func getEmbeddedTask(namespace string, args []string) *v1alpha1.Task {
	return tb.Task(embedTaskName, namespace,
		tb.TaskSpec(
			tb.TaskInputs(tb.InputsResource("docs", v1alpha1.PipelineResourceTypeGit)),
			tb.Step("read", "ubuntu",
				tb.Command("/bin/bash"),
				tb.Args("-c", "cat /workspace/docs/LICENSE"),
			),
			tb.Step("helloworld-busybox", "busybox", tb.Command(args...)),
		))
}

func getEmbeddedTaskRun(namespace string) *v1alpha1.TaskRun {
	testSpec := &v1alpha1.PipelineResourceSpec{
		Type: v1alpha1.PipelineResourceTypeGit,
		Params: []v1alpha1.Param{{
			Name:  "URL",
			Value: "https://github.com/knative/docs",
		}},
	}
	return tb.TaskRun(embedTaskRunName, namespace,
		tb.TaskRunSpec(
			tb.TaskRunInputs(
				tb.TaskRunInputsResource("docs", tb.TaskResourceBindingResourceSpec(testSpec)),
			),
			tb.TaskRunTaskRef(embedTaskName)))
}

// TestTaskRun_EmbeddedResource is an integration test that will verify a very simple "hello world" TaskRun can be
// executed with an embedded resource spec.
func TestTaskRun_EmbeddedResource(t *testing.T) {
	logger := logging.GetContextLogger(t.Name())
	c, namespace := setup(t, logger)
	t.Parallel()

	knativetest.CleanupOnInterrupt(func() { tearDown(t, logger, c, namespace) }, logger)
	defer tearDown(t, logger, c, namespace)

	logger.Infof("Creating Task and TaskRun in namespace %s", namespace)
	if _, err := c.TaskClient.Create(getEmbeddedTask(namespace, []string{"/bin/sh", "-c", fmt.Sprintf("echo %s", taskOutput)})); err != nil {
		t.Fatalf("Failed to create Task `%s`: %s", embedTaskName, err)
	}
	if _, err := c.TaskRunClient.Create(getEmbeddedTaskRun(namespace)); err != nil {
		t.Fatalf("Failed to create TaskRun `%s`: %s", embedTaskRunName, err)
	}

	logger.Infof("Waiting for TaskRun %s in namespace %s to complete", embedTaskRunName, namespace)
	if err := WaitForTaskRunState(c, embedTaskRunName, TaskRunSucceed(embedTaskRunName), "TaskRunSuccess"); err != nil {
		t.Errorf("Error waiting for TaskRun %s to finish: %s", embedTaskRunName, err)
	}

	// TODO(#127) Currently we have no reliable access to logs from the TaskRun so we'll assume successful
	// completion of the TaskRun means the TaskRun did what it was intended.
}
<|endoftext|>


<commit_before>package jobs

import (
	"fmt"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/liam-lai/ptt-alertor/line"
	"github.com/liam-lai/ptt-alertor/mail"
	"github.com/liam-lai/ptt-alertor/messenger"
	"github.com/liam-lai/ptt-alertor/models/ptt/article"
	board "github.com/liam-lai/ptt-alertor/models/ptt/board/redis"
	user "github.com/liam-lai/ptt-alertor/models/user/redis"
)

const checkBoardDuration = 5

type Checker struct {
	email      string
	line       string
	lineNotify string
	messenger  string
	board      string
	keyword    string
	author     string
	articles   article.Articles
}

func (cker Checker) String() string {
	subType := "關鍵字"
	subText := cker.keyword
	if cker.author != "" {
		subType = "作者"
		subText = cker.author
	}
	return fmt.Sprintf("\r\n看版：%s；%s：%s%s", cker.board, subType, subText, cker.articles.String())
}

func (cker Checker) Run() {
	boardCh := make(chan *board.Board)
	go func() {
		for {
			bds := new(board.Board).All()
			for _, bd := range bds[:len(bds)/2] {
				go checkNewArticle(bd, boardCh)
			}
			time.Sleep(checkBoardDuration * time.Second)
			for _, bd := range bds[len(bds)/2:] {
				go checkNewArticle(bd, boardCh)
			}
			time.Sleep(checkBoardDuration * time.Second)
		}
	}()
	ckerCh := make(chan Checker)

	for {
		select {
		case bd := <-boardCh:
			checkSubscriber(bd, cker, ckerCh)
		case m := <-ckerCh:
			sendMessage(m)
		}
	}
}

func checkNewArticle(bd *board.Board, boardCh chan *board.Board) {
	bd.WithNewArticles()
	if len(bd.NewArticles) != 0 {
		bd.Articles = bd.OnlineArticles()
		log.WithField("board", bd.Name).Info("Updated Articles")
		bd.Save()
		boardCh <- bd
	}
}

func checkSubscriber(bd *board.Board, cker Checker, ckerCh chan Checker) {
	users := new(user.User).All()
	for _, user := range users {
		if user.Enable {
			cker.email = user.Profile.Email
			cker.line = user.Profile.Line
			cker.lineNotify = user.Profile.LineAccessToken
			cker.messenger = user.Profile.Messenger
			go subscribeChecker(user, bd, cker, ckerCh)
		}
	}
}

func subscribeChecker(user *user.User, bd *board.Board, cker Checker, ckerCh chan Checker) {
	for _, sub := range user.Subscribes {
		if bd.Name == sub.Board {
			cker.board = sub.Board
			for _, keyword := range sub.Keywords {
				go keywordChecker(keyword, bd, cker, ckerCh)
			}
			for _, author := range sub.Authors {
				go authorChecker(author, bd, cker, ckerCh)
			}
		}
	}
}

func keywordChecker(keyword string, bd *board.Board, cker Checker, ckerCh chan Checker) {
	keywordArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if newAtcl.MatchKeyword(keyword) {
			newAtcl.Author = ""
			keywordArticles = append(keywordArticles, newAtcl)
		}
	}
	if len(keywordArticles) != 0 {
		cker.keyword = keyword
		cker.articles = keywordArticles
		ckerCh <- cker
	}
}

func authorChecker(author string, bd *board.Board, cker Checker, ckerCh chan Checker) {
	authorArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if strings.EqualFold(newAtcl.Author, author) {
			authorArticles = append(authorArticles, newAtcl)
		}
	}
	if len(authorArticles) != 0 {
		cker.author = author
		cker.articles = authorArticles
		ckerCh <- cker
	}

}

func sendMessage(cker Checker) {
	var account string
	var platform string
	subType := "keyword"
	word := cker.keyword

	if cker.author != "" {
		subType = "author"
		word = cker.author
	}

	if cker.email != "" {
		account = cker.email
		platform = "mail"
		sendMail(cker)
	}
	if cker.lineNotify != "" {
		account = cker.line
		platform = "line"
		sendLineNotify(cker)
	}
	if cker.messenger != "" {
		account = cker.messenger
		platform = "messenger"
		sendMessenger(cker)
	}
	log.WithFields(log.Fields{
		"account":  account,
		"platform": platform,
		"board":    cker.board,
		"type":     subType,
		"word":     word,
	}).Info("Message Sent")
}

func sendMail(cker Checker) {
	m := new(mail.Mail)
	m.Title.BoardName = cker.board
	m.Title.Keyword = cker.keyword
	m.Body.Articles = cker.articles
	m.Receiver = cker.email

	m.Send()
}

func sendLine(cker Checker) {
	line.PushTextMessage(cker.line, cker.String())
}

func sendLineNotify(cker Checker) {
	line.Notify(cker.lineNotify, cker.String())
}

func sendMessenger(cker Checker) {
	m := messenger.New()
	m.SendTextMessage(cker.messenger, cker.String())
}
<commit_msg>change duration<commit_after>package jobs

import (
	"fmt"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/liam-lai/ptt-alertor/line"
	"github.com/liam-lai/ptt-alertor/mail"
	"github.com/liam-lai/ptt-alertor/messenger"
	"github.com/liam-lai/ptt-alertor/models/ptt/article"
	board "github.com/liam-lai/ptt-alertor/models/ptt/board/redis"
	user "github.com/liam-lai/ptt-alertor/models/user/redis"
)

const checkBoardDuration = 30

type Checker struct {
	email      string
	line       string
	lineNotify string
	messenger  string
	board      string
	keyword    string
	author     string
	articles   article.Articles
}

func (cker Checker) String() string {
	subType := "關鍵字"
	subText := cker.keyword
	if cker.author != "" {
		subType = "作者"
		subText = cker.author
	}
	return fmt.Sprintf("\r\n看版：%s；%s：%s%s", cker.board, subType, subText, cker.articles.String())
}

func (cker Checker) Run() {
	boardCh := make(chan *board.Board)
	go func() {
		for {
			bds := new(board.Board).All()
			for _, bd := range bds[:len(bds)/2] {
				go checkNewArticle(bd, boardCh)
			}
			time.Sleep(checkBoardDuration * time.Second)
			for _, bd := range bds[len(bds)/2:] {
				go checkNewArticle(bd, boardCh)
			}
			time.Sleep(checkBoardDuration * time.Second)
		}
	}()
	ckerCh := make(chan Checker)

	for {
		select {
		case bd := <-boardCh:
			checkSubscriber(bd, cker, ckerCh)
		case m := <-ckerCh:
			sendMessage(m)
		}
	}
}

func checkNewArticle(bd *board.Board, boardCh chan *board.Board) {
	bd.WithNewArticles()
	if len(bd.NewArticles) != 0 {
		bd.Articles = bd.OnlineArticles()
		log.WithField("board", bd.Name).Info("Updated Articles")
		bd.Save()
		boardCh <- bd
	}
}

func checkSubscriber(bd *board.Board, cker Checker, ckerCh chan Checker) {
	users := new(user.User).All()
	for _, user := range users {
		if user.Enable {
			cker.email = user.Profile.Email
			cker.line = user.Profile.Line
			cker.lineNotify = user.Profile.LineAccessToken
			cker.messenger = user.Profile.Messenger
			go subscribeChecker(user, bd, cker, ckerCh)
		}
	}
}

func subscribeChecker(user *user.User, bd *board.Board, cker Checker, ckerCh chan Checker) {
	for _, sub := range user.Subscribes {
		if bd.Name == sub.Board {
			cker.board = sub.Board
			for _, keyword := range sub.Keywords {
				go keywordChecker(keyword, bd, cker, ckerCh)
			}
			for _, author := range sub.Authors {
				go authorChecker(author, bd, cker, ckerCh)
			}
		}
	}
}

func keywordChecker(keyword string, bd *board.Board, cker Checker, ckerCh chan Checker) {
	keywordArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if newAtcl.MatchKeyword(keyword) {
			newAtcl.Author = ""
			keywordArticles = append(keywordArticles, newAtcl)
		}
	}
	if len(keywordArticles) != 0 {
		cker.keyword = keyword
		cker.articles = keywordArticles
		ckerCh <- cker
	}
}

func authorChecker(author string, bd *board.Board, cker Checker, ckerCh chan Checker) {
	authorArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if strings.EqualFold(newAtcl.Author, author) {
			authorArticles = append(authorArticles, newAtcl)
		}
	}
	if len(authorArticles) != 0 {
		cker.author = author
		cker.articles = authorArticles
		ckerCh <- cker
	}

}

func sendMessage(cker Checker) {
	var account string
	var platform string
	subType := "keyword"
	word := cker.keyword

	if cker.author != "" {
		subType = "author"
		word = cker.author
	}

	if cker.email != "" {
		account = cker.email
		platform = "mail"
		sendMail(cker)
	}
	if cker.lineNotify != "" {
		account = cker.line
		platform = "line"
		sendLineNotify(cker)
	}
	if cker.messenger != "" {
		account = cker.messenger
		platform = "messenger"
		sendMessenger(cker)
	}
	log.WithFields(log.Fields{
		"account":  account,
		"platform": platform,
		"board":    cker.board,
		"type":     subType,
		"word":     word,
	}).Info("Message Sent")
}

func sendMail(cker Checker) {
	m := new(mail.Mail)
	m.Title.BoardName = cker.board
	m.Title.Keyword = cker.keyword
	m.Body.Articles = cker.articles
	m.Receiver = cker.email

	m.Send()
}

func sendLine(cker Checker) {
	line.PushTextMessage(cker.line, cker.String())
}

func sendLineNotify(cker Checker) {
	line.Notify(cker.lineNotify, cker.String())
}

func sendMessenger(cker Checker) {
	m := messenger.New()
	m.SendTextMessage(cker.messenger, cker.String())
}
<|endoftext|>


<commit_before>// Package journal provides write bindings to the systemd journal
package journal

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"strconv"
	"strings"
	"syscall"
	"encoding/binary"
)

// Priority of a journal message
type Priority int

const (
	PriEmerg Priority = iota
	PriAlert
	PriCrit
	PriErr
	PriWarning
	PriNotice
	PriInfo
	PriDebug
)

var conn net.Conn

func init() {
	var err error
	conn, err = net.Dial("unixgram", "/run/systemd/journal/socket")
	if err != nil {
		journalError(err.Error())
	}
}

// Enabled returns true iff the systemd journal is available for logging
func Enabled() bool {
	return conn != nil
}

// Send a message to the systemd journal. vars is a map of journald fields to
// values.  Fields must be composed of uppercase letters, numbers, and
// underscores, but must not start with an underscore. Within these
// restrictions, any arbitrary field name may be used.  Some names have special
// significance: see the journalctl documentation
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
// for more details.  vars may be nil.
func Send(message string, priority Priority, vars map[string]string) error {
	if conn == nil {
		return journalError("could not connect to journald socket")
	}

	data := new(bytes.Buffer)
	appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
	appendVariable(data, "MESSAGE", message)
	for k, v := range vars {
		appendVariable(data, k, v)
	}

	_, err := io.Copy(conn, data)
	if err != nil && isSocketSpaceError(err) {
		file, err := tempFd()
		if err != nil {
			return journalError(err.Error())
		}
		_, err = io.Copy(file, data)
		if err != nil {
			return journalError(err.Error())
		}

		rights := syscall.UnixRights(int(file.Fd()))

		/* this connection should always be a UnixConn, but better safe than sorry */
		unixConn, ok := conn.(*net.UnixConn)
		if !ok {
			return journalError("can't send file through non-Unix connection")
		}
		unixConn.WriteMsgUnix([]byte{}, rights, nil)
	} else if err != nil {
		return journalError(err.Error())
	}
	return nil
}

func appendVariable(w io.Writer, name, value string) {
	if !validVarName(name) {
		journalError("variable name contains invalid character, ignoring")
	}
	if strings.ContainsRune(value, '\n') {
		/* When the value contains a newline, we write:
		 * - the variable name, followed by a newline
		 * - the size (in 64bit little endian format)
		 * - the data, followed by a newline
		 */
		fmt.Fprintln(w, name)
		binary.Write(w, binary.LittleEndian, uint64(len(value)))
		fmt.Fprintln(w, value)
	} else {
		/* just write the variable and value all on one line */
		fmt.Fprintf(w, "%s=%s\n", name, value)
	}
}

func validVarName(name string) bool {
	/* The variable name must be in uppercase and consist only of characters,
	 * numbers and underscores, and may not begin with an underscore. (from the docs)
	 */
	valid := true
	valid = valid && name[0] != '_'
	for _, c := range name {
		valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_'
	}
	return valid
}

func isSocketSpaceError(err error) bool {
	opErr, ok := err.(*net.OpError)
	if !ok {
		return false
	}

	sysErr, ok := opErr.Err.(syscall.Errno)
	if !ok {
		return false
	}

	return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
}

func tempFd() (*os.File, error) {
	file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
	if err != nil {
		return nil, err
	}
	syscall.Unlink(file.Name())
	if err != nil {
		return nil, err
	}
	return file, nil
}

func journalError(s string) error {
	s = "journal error: " + s
	fmt.Fprintln(os.Stderr, s)
	return errors.New(s)
}
<commit_msg>Remove initial error output when journal does not exist<commit_after>// Package journal provides write bindings to the systemd journal
package journal

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"os"
	"strconv"
	"strings"
	"syscall"
	"encoding/binary"
)

// Priority of a journal message
type Priority int

const (
	PriEmerg Priority = iota
	PriAlert
	PriCrit
	PriErr
	PriWarning
	PriNotice
	PriInfo
	PriDebug
)

var conn net.Conn

func init() {
	var err error
	conn, err = net.Dial("unixgram", "/run/systemd/journal/socket")
}

// Enabled returns true iff the systemd journal is available for logging
func Enabled() bool {
	return conn != nil
}

// Send a message to the systemd journal. vars is a map of journald fields to
// values.  Fields must be composed of uppercase letters, numbers, and
// underscores, but must not start with an underscore. Within these
// restrictions, any arbitrary field name may be used.  Some names have special
// significance: see the journalctl documentation
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
// for more details.  vars may be nil.
func Send(message string, priority Priority, vars map[string]string) error {
	if conn == nil {
		return journalError("could not connect to journald socket")
	}

	data := new(bytes.Buffer)
	appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
	appendVariable(data, "MESSAGE", message)
	for k, v := range vars {
		appendVariable(data, k, v)
	}

	_, err := io.Copy(conn, data)
	if err != nil && isSocketSpaceError(err) {
		file, err := tempFd()
		if err != nil {
			return journalError(err.Error())
		}
		_, err = io.Copy(file, data)
		if err != nil {
			return journalError(err.Error())
		}

		rights := syscall.UnixRights(int(file.Fd()))

		/* this connection should always be a UnixConn, but better safe than sorry */
		unixConn, ok := conn.(*net.UnixConn)
		if !ok {
			return journalError("can't send file through non-Unix connection")
		}
		unixConn.WriteMsgUnix([]byte{}, rights, nil)
	} else if err != nil {
		return journalError(err.Error())
	}
	return nil
}

func appendVariable(w io.Writer, name, value string) {
	if !validVarName(name) {
		journalError("variable name contains invalid character, ignoring")
	}
	if strings.ContainsRune(value, '\n') {
		/* When the value contains a newline, we write:
		 * - the variable name, followed by a newline
		 * - the size (in 64bit little endian format)
		 * - the data, followed by a newline
		 */
		fmt.Fprintln(w, name)
		binary.Write(w, binary.LittleEndian, uint64(len(value)))
		fmt.Fprintln(w, value)
	} else {
		/* just write the variable and value all on one line */
		fmt.Fprintf(w, "%s=%s\n", name, value)
	}
}

func validVarName(name string) bool {
	/* The variable name must be in uppercase and consist only of characters,
	 * numbers and underscores, and may not begin with an underscore. (from the docs)
	 */
	valid := true
	valid = valid && name[0] != '_'
	for _, c := range name {
		valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_'
	}
	return valid
}

func isSocketSpaceError(err error) bool {
	opErr, ok := err.(*net.OpError)
	if !ok {
		return false
	}

	sysErr, ok := opErr.Err.(syscall.Errno)
	if !ok {
		return false
	}

	return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
}

func tempFd() (*os.File, error) {
	file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
	if err != nil {
		return nil, err
	}
	syscall.Unlink(file.Name())
	if err != nil {
		return nil, err
	}
	return file, nil
}

func journalError(s string) error {
	s = "journal error: " + s
	fmt.Fprintln(os.Stderr, s)
	return errors.New(s)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes 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 csicommon

import (
	"fmt"

	"github.com/container-storage-interface/spec/lib/go/csi"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"k8s.io/klog/v2"

	"github.com/ceph/ceph-csi/internal/util"
)

// CSIDriver stores driver information.
type CSIDriver struct {
	name    string
	nodeID  string
	version string
	// topology constraints that this nodeserver will advertise
	topology     map[string]string
	capabilities []*csi.ControllerServiceCapability
	vc           []*csi.VolumeCapability_AccessMode
}

// NewCSIDriver Creates a NewCSIDriver object. Assumes vendor
// version is equal to driver version &  does not support optional
// driver plugin info manifest field. Refer to CSI spec for more details.
func NewCSIDriver(name, v, nodeID string) *CSIDriver {
	if name == "" {
		klog.Errorf("Driver name missing")

		return nil
	}

	if nodeID == "" {
		klog.Errorf("NodeID missing")

		return nil
	}
	// TODO version format and validation
	if v == "" {
		klog.Errorf("Version argument missing")

		return nil
	}

	driver := CSIDriver{
		name:    name,
		version: v,
		nodeID:  nodeID,
	}

	return &driver
}

// ValidateControllerServiceRequest validates the controller
// plugin capabilities.
func (d *CSIDriver) ValidateControllerServiceRequest(c csi.ControllerServiceCapability_RPC_Type) error {
	if c == csi.ControllerServiceCapability_RPC_UNKNOWN {
		return nil
	}

	for _, capability := range d.capabilities {
		if c == capability.GetRpc().GetType() {
			return nil
		}
	}

	return status.Error(codes.InvalidArgument, fmt.Sprintf("%s", c)) //nolint
}

// AddControllerServiceCapabilities stores the controller capabilities
// in driver object.
func (d *CSIDriver) AddControllerServiceCapabilities(cl []csi.ControllerServiceCapability_RPC_Type) {
	var csc []*csi.ControllerServiceCapability

	for _, c := range cl {
		util.DefaultLog("Enabling controller service capability: %v", c.String())
		csc = append(csc, NewControllerServiceCapability(c))
	}

	d.capabilities = csc
}

// AddVolumeCapabilityAccessModes stores volume access modes.
func (d *CSIDriver) AddVolumeCapabilityAccessModes(
	vc []csi.VolumeCapability_AccessMode_Mode) []*csi.VolumeCapability_AccessMode {
	var vca []*csi.VolumeCapability_AccessMode
	for _, c := range vc {
		util.DefaultLog("Enabling volume access mode: %v", c.String())
		vca = append(vca, NewVolumeCapabilityAccessMode(c))
	}
	d.vc = vca

	return vca
}

// GetVolumeCapabilityAccessModes returns access modes.
func (d *CSIDriver) GetVolumeCapabilityAccessModes() []*csi.VolumeCapability_AccessMode {
	return d.vc
}
<commit_msg>cleanup: allocate slice with known size<commit_after>/*
Copyright 2017 The Kubernetes 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 csicommon

import (
	"fmt"

	"github.com/container-storage-interface/spec/lib/go/csi"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"k8s.io/klog/v2"

	"github.com/ceph/ceph-csi/internal/util"
)

// CSIDriver stores driver information.
type CSIDriver struct {
	name    string
	nodeID  string
	version string
	// topology constraints that this nodeserver will advertise
	topology     map[string]string
	capabilities []*csi.ControllerServiceCapability
	vc           []*csi.VolumeCapability_AccessMode
}

// NewCSIDriver Creates a NewCSIDriver object. Assumes vendor
// version is equal to driver version &  does not support optional
// driver plugin info manifest field. Refer to CSI spec for more details.
func NewCSIDriver(name, v, nodeID string) *CSIDriver {
	if name == "" {
		klog.Errorf("Driver name missing")

		return nil
	}

	if nodeID == "" {
		klog.Errorf("NodeID missing")

		return nil
	}
	// TODO version format and validation
	if v == "" {
		klog.Errorf("Version argument missing")

		return nil
	}

	driver := CSIDriver{
		name:    name,
		version: v,
		nodeID:  nodeID,
	}

	return &driver
}

// ValidateControllerServiceRequest validates the controller
// plugin capabilities.
func (d *CSIDriver) ValidateControllerServiceRequest(c csi.ControllerServiceCapability_RPC_Type) error {
	if c == csi.ControllerServiceCapability_RPC_UNKNOWN {
		return nil
	}

	for _, capability := range d.capabilities {
		if c == capability.GetRpc().GetType() {
			return nil
		}
	}

	return status.Error(codes.InvalidArgument, fmt.Sprintf("%s", c)) //nolint
}

// AddControllerServiceCapabilities stores the controller capabilities
// in driver object.
func (d *CSIDriver) AddControllerServiceCapabilities(cl []csi.ControllerServiceCapability_RPC_Type) {
	csc := make([]*csi.ControllerServiceCapability, 0, len(cl))

	for _, c := range cl {
		util.DefaultLog("Enabling controller service capability: %v", c.String())
		csc = append(csc, NewControllerServiceCapability(c))
	}

	d.capabilities = csc
}

// AddVolumeCapabilityAccessModes stores volume access modes.
func (d *CSIDriver) AddVolumeCapabilityAccessModes(
	vc []csi.VolumeCapability_AccessMode_Mode) []*csi.VolumeCapability_AccessMode {
	vca := make([]*csi.VolumeCapability_AccessMode, 0, len(vc))
	for _, c := range vc {
		util.DefaultLog("Enabling volume access mode: %v", c.String())
		vca = append(vca, NewVolumeCapabilityAccessMode(c))
	}
	d.vc = vca

	return vca
}

// GetVolumeCapabilityAccessModes returns access modes.
func (d *CSIDriver) GetVolumeCapabilityAccessModes() []*csi.VolumeCapability_AccessMode {
	return d.vc
}
<|endoftext|>


<commit_before>package main

//go:generate go run generate.go protocol/input/json.json ../../aws/protocol/jsonrpc/jsonrpc_input_test.go
//go:generate go run generate.go protocol/input/query.json ../../aws/protocol/query/build_test.go

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"text/template"

	"github.com/awslabs/aws-sdk-go/internal/model/api"
	"github.com/awslabs/aws-sdk-go/internal/util"
)

type TestSuite struct {
	*api.API
	Description string
	Cases       []TestCase
	title       string
}

type TestCase struct {
	*TestSuite
	Given      *api.Operation
	Params     interface{}     `json:",omitempty"`
	Data       interface{}     `json:"result,omitempty"`
	InputTest  TestExpectation `json:"serialized"`
	OutputTest TestExpectation `json:"response"`
}

type TestExpectation struct {
	Body       string
	URI        string
	Headers    map[string]string
	StatusCode uint `json:"status_code"`
}

const preamble = `
var _ bytes.Buffer // always import bytes
var _ http.Request
`

var reStripSpace = regexp.MustCompile(`\s(\w)`)

var reImportRemoval = regexp.MustCompile(`(?s:import \((.+?)\))`)

func removeImports(code string) string {
	return reImportRemoval.ReplaceAllString(code, "")
}

var extraImports = []string{
	"bytes",
	"encoding/json",
	"io/ioutil",
	"net/http",
	"testing",
	"github.com/awslabs/aws-sdk-go/internal/util",
	"github.com/stretchr/testify/assert",
}

func addImports(code string) string {
	importNames := make([]string, len(extraImports))
	for i, n := range extraImports {
		importNames[i] = fmt.Sprintf("%q", n)
	}
	str := reImportRemoval.ReplaceAllString(code, "import (\n$1\n"+strings.Join(importNames, "\n")+")")
	return str
}

func (t *TestSuite) TestSuite() string {
	var buf bytes.Buffer

	t.title = reStripSpace.ReplaceAllStringFunc(t.Description, func(x string) string {
		return strings.ToUpper(x[1:])
	})

	for idx, c := range t.Cases {
		c.TestSuite = t
		buf.WriteString(c.TestCase(idx) + "\n")
	}
	return util.GoFmt(buf.String())
}

var tplInputTestCase = template.Must(template.New("inputcase").Parse(`
func Test{{ .OpName }}(t *testing.T) {
	svc := New{{ .TestCase.TestSuite.API.StructName }}(nil)

	var input {{ .Given.InputRef.ShapeName }}
	json.Unmarshal([]byte({{ .ParamsString }}), &input)
	req := svc.{{ .Given.ExportedName }}Request(&input)
	r := req.HTTPRequest

	// build request
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.Build(req)
	assert.NoError(t, req.Error)

	// assert body
	body, _ := ioutil.ReadAll(r.Body)
	assert.Equal(t, util.Trim({{ .Body }}), util.Trim(string(body)))

	// assert URL
	assert.Equal(t, "{{ .TestCase.InputTest.URI }}", r.URL.Path)

	// assert headers
{{ range $k, $v := .TestCase.InputTest.Headers }}assert.Equal(t, "{{ $v }}", r.Header.Get("{{ $k }}"))
{{ end }}
}
`))

type tplInputTestCaseData struct {
	*TestCase
	Body, OpName, ParamsString string
}

var tplOutputTestCase = template.Must(template.New("outputcase").Parse(`
func Test{{ .OpName }}(t *testing.T) {
	svc := New{{ .TestCase.TestSuite.API.StructName }}(nil)

	buf := bytes.NewReader([]byte({{ .Body }}))
	req, _ := svc.{{ .Given.ExportedName }}Request()
	req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf)}

	// unmarshal response
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.Unmarshal(req)
	assert.NoError(t, req.Error)

	// assert response
	buf, _ := json.Marshal(req.Data)
	assert.Equal(t, util.Trim({{ .ResponseString }}), util.Trim(string(buf)))

	// assert headers
{{ range $k, $v := .TestCase.OutputTest.Headers }}assert.Equal(t, "{{ $v }}", req.HTTPResponse.Header.Get("{{ $k }}"))
{{ end }}
}
`))

type tplOutputTestCaseData struct {
	*TestCase
	Body, OpName, ResponseString string
}

func (i *TestCase) TestCase(idx int) string {
	var buf bytes.Buffer

	opName := i.API.StructName() + i.TestSuite.title + "Case" + strconv.Itoa(idx+1)

	if i.Params != nil { // input test
		// query test should sort body as form encoded values
		switch i.API.Metadata.Protocol {
		case "query", "ec2":
			m, _ := url.ParseQuery(i.InputTest.Body)
			i.InputTest.Body = m.Encode()
		}

		pBuf, _ := json.Marshal(i.Params)
		input := tplInputTestCaseData{
			TestCase:     i,
			Body:         fmt.Sprintf("%q", i.InputTest.Body),
			OpName:       strings.ToUpper(opName[0:1]) + opName[1:],
			ParamsString: fmt.Sprintf("%q", pBuf),
		}

		if err := tplInputTestCase.Execute(&buf, input); err != nil {
			panic(err)
		}
	} else {
		pBuf, _ := json.Marshal(i.Data)
		output := tplOutputTestCaseData{
			TestCase:       i,
			Body:           fmt.Sprintf("%q", i.OutputTest.Body),
			OpName:         strings.ToUpper(opName[0:1]) + opName[1:],
			ResponseString: fmt.Sprintf("%q", pBuf),
		}

		if err := tplOutputTestCase.Execute(&buf, output); err != nil {
			panic(err)
		}
	}

	return util.GoFmt(buf.String())
}

func GenerateTestSuite(filename string) string {
	inout := "Input"
	if strings.Contains(filename, "/output/") {
		inout = "Output"
	}

	var suites []TestSuite
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	err = json.NewDecoder(f).Decode(&suites)
	if err != nil {
		panic(err)
	}

	var buf bytes.Buffer
	buf.WriteString("package " + suites[0].ProtocolPackage() + "_test\n\n")

	var innerBuf bytes.Buffer
	innerBuf.WriteString("//\n// Tests begin here\n//\n\n\n")

	for i, suite := range suites {
		svcPrefix := inout + "Service" + strconv.Itoa(i+1)
		suite.API.Metadata.ServiceAbbreviation = svcPrefix + "ProtocolTest"
		suite.API.Operations = map[string]*api.Operation{}
		for idx, c := range suite.Cases {
			c.Given.ExportedName = svcPrefix + "TestCaseOperation" + strconv.Itoa(idx+1)
			suite.API.Operations[c.Given.ExportedName] = c.Given
		}

		suite.API.NoInflections = true // don't require inflections
		suite.API.Setup()
		suite.API.Metadata.EndpointPrefix = suite.API.PackageName()

		for n, s := range suite.API.Shapes {
			s.Rename(svcPrefix + "TestShape" + n)
		}

		svcCode := addImports(suite.API.ServiceGoCode())
		if i == 0 {
			importMatch := reImportRemoval.FindStringSubmatch(svcCode)
			buf.WriteString(importMatch[0] + "\n\n")
			buf.WriteString(preamble + "\n\n")
		}
		svcCode = removeImports(svcCode)
		svcCode = strings.Replace(svcCode, "func New(", "func New"+suite.API.StructName()+"(", -1)

		buf.WriteString(svcCode + "\n\n")
		buf.WriteString(removeImports(suite.API.APIGoCode()) + "\n\n")
		innerBuf.WriteString(suite.TestSuite() + "\n")
	}

	return util.GoFmt(buf.String() + innerBuf.String())
}

func main() {
	out := GenerateTestSuite(os.Args[1])
	if len(os.Args) == 3 {
		f, err := os.Create(os.Args[2])
		defer f.Close()
		if err != nil {
			panic(err)
		}
		f.WriteString(out + "\n")
	} else {
		fmt.Println(out)
	}
}
<commit_msg>Change the way response is asserted in generated tests<commit_after>package main

//go:generate go run generate.go protocol/input/json.json ../../aws/protocol/jsonrpc/jsonrpc_input_test.go
//go:generate go run generate.go protocol/input/query.json ../../aws/protocol/query/build_test.go

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"text/template"

	"github.com/awslabs/aws-sdk-go/internal/model/api"
	"github.com/awslabs/aws-sdk-go/internal/util"
)

type TestSuite struct {
	*api.API
	Description string
	Cases       []TestCase
	title       string
}

type TestCase struct {
	*TestSuite
	Given      *api.Operation
	Params     interface{}     `json:",omitempty"`
	Data       interface{}     `json:"result,omitempty"`
	InputTest  TestExpectation `json:"serialized"`
	OutputTest TestExpectation `json:"response"`
}

type TestExpectation struct {
	Body       string
	URI        string
	Headers    map[string]string
	StatusCode uint `json:"status_code"`
}

const preamble = `
var _ bytes.Buffer // always import bytes
var _ http.Request
`

var reStripSpace = regexp.MustCompile(`\s(\w)`)

var reImportRemoval = regexp.MustCompile(`(?s:import \((.+?)\))`)

func removeImports(code string) string {
	return reImportRemoval.ReplaceAllString(code, "")
}

var extraImports = []string{
	"bytes",
	"encoding/json",
	"io/ioutil",
	"net/http",
	"testing",
	"github.com/awslabs/aws-sdk-go/internal/util",
	"github.com/stretchr/testify/assert",
}

func addImports(code string) string {
	importNames := make([]string, len(extraImports))
	for i, n := range extraImports {
		importNames[i] = fmt.Sprintf("%q", n)
	}
	str := reImportRemoval.ReplaceAllString(code, "import (\n$1\n"+strings.Join(importNames, "\n")+")")
	return str
}

func (t *TestSuite) TestSuite() string {
	var buf bytes.Buffer

	t.title = reStripSpace.ReplaceAllStringFunc(t.Description, func(x string) string {
		return strings.ToUpper(x[1:])
	})

	for idx, c := range t.Cases {
		c.TestSuite = t
		buf.WriteString(c.TestCase(idx) + "\n")
	}
	return util.GoFmt(buf.String())
}

var tplInputTestCase = template.Must(template.New("inputcase").Parse(`
func Test{{ .OpName }}(t *testing.T) {
	svc := New{{ .TestCase.TestSuite.API.StructName }}(nil)

	var input {{ .Given.InputRef.ShapeName }}
	json.Unmarshal([]byte({{ .ParamsString }}), &input)
	req := svc.{{ .Given.ExportedName }}Request(&input)
	r := req.HTTPRequest

	// build request
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.Build(req)
	assert.NoError(t, req.Error)

	// assert body
	body, _ := ioutil.ReadAll(r.Body)
	assert.Equal(t, util.Trim({{ .Body }}), util.Trim(string(body)))

	// assert URL
	assert.Equal(t, "{{ .TestCase.InputTest.URI }}", r.URL.Path)

	// assert headers
{{ range $k, $v := .TestCase.InputTest.Headers }}assert.Equal(t, "{{ $v }}", r.Header.Get("{{ $k }}"))
{{ end }}
}
`))

type tplInputTestCaseData struct {
	*TestCase
	Body, OpName, ParamsString string
}

var tplOutputTestCase = template.Must(template.New("outputcase").Parse(`
func Test{{ .OpName }}(t *testing.T) {
	svc := New{{ .TestCase.TestSuite.API.StructName }}(nil)

	buf := bytes.NewReader([]byte({{ .Body }}))
	req, _ := svc.{{ .Given.ExportedName }}Request()
	req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf)}

	// unmarshal response
	{{ .TestCase.TestSuite.API.ProtocolPackage }}.Unmarshal(req)
	assert.NoError(t, req.Error)

	// assert response
	jBuf, _ := json.Marshal(req.Data)
	assert.Equal(t, util.Trim({{ .ResponseString }}), util.Trim(string(jBuf)))

	// assert headers
{{ range $k, $v := .TestCase.OutputTest.Headers }}assert.Equal(t, "{{ $v }}", req.HTTPResponse.Header.Get("{{ $k }}"))
{{ end }}
}
`))

type tplOutputTestCaseData struct {
	*TestCase
	Body, OpName, ResponseString string
}

func (i *TestCase) TestCase(idx int) string {
	var buf bytes.Buffer

	opName := i.API.StructName() + i.TestSuite.title + "Case" + strconv.Itoa(idx+1)

	if i.Params != nil { // input test
		// query test should sort body as form encoded values
		switch i.API.Metadata.Protocol {
		case "query", "ec2":
			m, _ := url.ParseQuery(i.InputTest.Body)
			i.InputTest.Body = m.Encode()
		}

		pBuf, _ := json.Marshal(i.Params)
		input := tplInputTestCaseData{
			TestCase:     i,
			Body:         fmt.Sprintf("%q", i.InputTest.Body),
			OpName:       strings.ToUpper(opName[0:1]) + opName[1:],
			ParamsString: fmt.Sprintf("%q", pBuf),
		}

		if err := tplInputTestCase.Execute(&buf, input); err != nil {
			panic(err)
		}
	} else {
		pBuf, _ := json.Marshal(i.Data)
		output := tplOutputTestCaseData{
			TestCase:       i,
			Body:           fmt.Sprintf("%q", i.OutputTest.Body),
			OpName:         strings.ToUpper(opName[0:1]) + opName[1:],
			ResponseString: fmt.Sprintf("%q", pBuf),
		}

		if err := tplOutputTestCase.Execute(&buf, output); err != nil {
			panic(err)
		}
	}

	return util.GoFmt(buf.String())
}

func GenerateTestSuite(filename string) string {
	inout := "Input"
	if strings.Contains(filename, "/output/") {
		inout = "Output"
	}

	var suites []TestSuite
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	err = json.NewDecoder(f).Decode(&suites)
	if err != nil {
		panic(err)
	}

	var buf bytes.Buffer
	buf.WriteString("package " + suites[0].ProtocolPackage() + "_test\n\n")

	var innerBuf bytes.Buffer
	innerBuf.WriteString("//\n// Tests begin here\n//\n\n\n")

	for i, suite := range suites {
		svcPrefix := inout + "Service" + strconv.Itoa(i+1)
		suite.API.Metadata.ServiceAbbreviation = svcPrefix + "ProtocolTest"
		suite.API.Operations = map[string]*api.Operation{}
		for idx, c := range suite.Cases {
			c.Given.ExportedName = svcPrefix + "TestCaseOperation" + strconv.Itoa(idx+1)
			suite.API.Operations[c.Given.ExportedName] = c.Given
		}

		suite.API.NoInflections = true // don't require inflections
		suite.API.Setup()
		suite.API.Metadata.EndpointPrefix = suite.API.PackageName()

		for n, s := range suite.API.Shapes {
			s.Rename(svcPrefix + "TestShape" + n)
		}

		svcCode := addImports(suite.API.ServiceGoCode())
		if i == 0 {
			importMatch := reImportRemoval.FindStringSubmatch(svcCode)
			buf.WriteString(importMatch[0] + "\n\n")
			buf.WriteString(preamble + "\n\n")
		}
		svcCode = removeImports(svcCode)
		svcCode = strings.Replace(svcCode, "func New(", "func New"+suite.API.StructName()+"(", -1)

		buf.WriteString(svcCode + "\n\n")
		buf.WriteString(removeImports(suite.API.APIGoCode()) + "\n\n")
		innerBuf.WriteString(suite.TestSuite() + "\n")
	}

	return util.GoFmt(buf.String() + innerBuf.String())
}

func main() {
	out := GenerateTestSuite(os.Args[1])
	if len(os.Args) == 3 {
		f, err := os.Create(os.Args[2])
		defer f.Close()
		if err != nil {
			panic(err)
		}
		f.WriteString(out + "\n")
	} else {
		fmt.Println(out)
	}
}
<|endoftext|>