<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"log"
	"os"
	"text/template"

	"github.com/thoj/go-ircevent"
)

type NetworkConfig struct {
	Name    string
	Address string
	Channel string
}

type Configuration struct {
	Username  string
	Nicks     []string
	Networks  []NetworkConfig
	Forward   []string
	Templates map[string]string
}

type TemplateMap map[string]*template.Template

type NetworkConfigAll struct {
	Network   NetworkConfig
	Nicks     []string
	Username  string
	Sink      chan Message
	Reciver   chan Message
	Forward   []string
	Templates *TemplateMap
}

type Message struct {
	*irc.Event
	Eventcode string
	Network   string // Network's name, same as in NetworkConfig.Name
}

var logger = log.New(os.Stdout, "irc_bridge:", log.LstdFlags)

// load configuration fron JSON file into Configuration structure
func loadConfig(configPath string) *Configuration {
	configFile, err := os.Open(configPath)
	if err != nil {
		logger.Println(err.Error())
		logger.Fatal("Can't open config file.")
	}
	defer configFile.Close()

	var config Configuration
	jsonParser := json.NewDecoder(configFile)
	if err := jsonParser.Decode(&config); err != nil {
		logger.Println(err.Error())
		logger.Fatal("Can't parse config file.")
	}

	return &config
}

// Setup all callbacks for given connection.
func setupCallbacks(conn *irc.Connection, config *NetworkConfigAll) {
	NetworkName := config.Network.Name

	// join desired irc channel on connection success
	conn.AddCallback("001", func(e *irc.Event) {
		logger.Printf("[%s] // join -> %s\n", NetworkName, config.Network.Channel)
		conn.Join(config.Network.Channel)
	})

	// select next nick if current one is already in use
	nickIndex := 0
	conn.AddCallback("433", func(e *irc.Event) {
		if nickIndex < len(config.Nicks)-1 {
			nickIndex++
		} else {
			nickIndex = 0
		}
		logger.Printf("[%s] // 433 trying change nick to %s\n", NetworkName,
			config.Nicks[nickIndex])
		conn.Nick(config.Nicks[nickIndex])
	})

	// register all callbacks for events to retransmit
	for _, eventcode := range config.Forward {
		conn.AddCallback(eventcode, makeEventHandler(conn, eventcode, config))
	}
}

// return function that handles IRC event with given code
func makeEventHandler(conn *irc.Connection, eventcode string,
	config *NetworkConfigAll) func(*irc.Event) {
	return func(event *irc.Event) {
		// ignore events sent by myselfe
		if event.Nick == conn.GetNick() {
			return
		}

		message := Message{
			Network:   config.Network.Name,
			Eventcode: eventcode,
			Event:     event,
		}
		template := getTemplateForEventcode(eventcode, config.Templates)
		logger.Println(formatMessage(template, &message))
		config.Sink <- message
	}
}

// Convert given message to string usign given template.
func formatMessage(template *template.Template, message *Message) string {
	var buffer bytes.Buffer

	err := template.Execute(&buffer, message)
	if err != nil {
		logger.Fatalf("Invalid template: %s\n", err)
	}

	return buffer.String()
}

// Returns *template.Template from 'templates' that corresponds to 'code' or
// "default" pointer.
func getTemplateForEventcode(code string, templates *TemplateMap) *template.Template {
	template, ok := (*templates)[code]
	if !ok {
		template = (*templates)["default"]
	}
	return template
}

// Connect to single Network and join desired channel.
func makeConnection(config *NetworkConfigAll) {
	logger.Printf("[%s] (%s/%s)\n", config.Network.Name, config.Network.Address,
		config.Network.Channel)

	conn := irc.IRC(config.Nicks[0], config.Username)
	//conn.VerboseCallbackHandler = true
	conn.Debug = true

	err := conn.Connect(config.Network.Address)
	if err != nil {
		logger.Println(err.Error())
		logger.Fatalf("Can't connect to network %s.\n", config.Network.Name)
	}

	setupCallbacks(conn, config)

	go loopMessages(conn, config)
	go conn.Loop()
}

// wait for messages on config.Reciver and write to irc channel only
// those that are not recived on this Network/channel
func loopMessages(conn *irc.Connection, config *NetworkConfigAll) {
	for {
		message := <-config.Reciver
		// ignore messages sent by myselfe
		if message.Network == config.Network.Name {
			continue
		}

		// set topic
		if message.Eventcode == "TOPIC" {
			conn.SendRawf("TOPIC %s :%s (%s)\r\n", config.Network.Channel,
				message.Message(), message.Nick)
		}

		template := getTemplateForEventcode(message.Eventcode, config.Templates)
		text := formatMessage(template, &message)

		if len(text) > 0 {
			conn.Privmsg(config.Network.Channel, text)
		}
	}
}

// Initialize all connections. Returns list of channels for all connected
// Network/channel pair.
func makeConnections(config *Configuration, sink chan Message) []chan Message {
	recivers := make([]chan Message, len(config.Networks))
	templates := makeTemplates(config.Templates)

	for i, Network := range config.Networks {
		reciver := make(chan Message)
		NetworkConfig := &NetworkConfigAll{
			Network:   Network,
			Nicks:     config.Nicks,
			Username:  config.Username,
			Sink:      sink,
			Reciver:   reciver,
			Forward:   config.Forward,
			Templates: templates,
		}
		recivers[i] = reciver
		makeConnection(NetworkConfig)
	}
	return recivers
}

// Initialize template.Template object for each template defined in
// configuration.
func makeTemplates(definition map[string]string) *TemplateMap {
	result := make(TemplateMap)

	for key, value := range definition {
		tmpl, err := template.New(key).Parse(value)
		if err != nil {
			logger.Fatalf("Could not create template for '%s': %s\n", key, err)
		}
		result[key] = tmpl
	}

	return &result
}

// Write all messages recived from sink to all recivers.
func loop(sink chan Message, recivers []chan Message) {
	for message := range sink {
		for _, reciver := range recivers {
			reciver <- message
		}
	}
}

func main() {
	if len(os.Args) == 1 {
		logger.Fatal("No config file specified.")
	}

	config := loadConfig(os.Args[1])
	sink := make(chan Message)
	recivers := makeConnections(config, sink)

	loop(sink, recivers)
}
<commit_msg>Fix spelling error.<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"log"
	"os"
	"text/template"

	"github.com/thoj/go-ircevent"
)

type NetworkConfig struct {
	Name    string
	Address string
	Channel string
}

type Configuration struct {
	Username  string
	Nicks     []string
	Networks  []NetworkConfig
	Forward   []string
	Templates map[string]string
}

type TemplateMap map[string]*template.Template

type NetworkConfigAll struct {
	Network   NetworkConfig
	Nicks     []string
	Username  string
	Sink      chan Message
	Receiver  chan Message
	Forward   []string
	Templates *TemplateMap
}

type Message struct {
	*irc.Event
	Eventcode string
	Network   string // Network's name, same as in NetworkConfig.Name
}

var logger = log.New(os.Stdout, "irc_bridge:", log.LstdFlags)

// load configuration fron JSON file into Configuration structure
func loadConfig(configPath string) *Configuration {
	configFile, err := os.Open(configPath)
	if err != nil {
		logger.Println(err.Error())
		logger.Fatal("Can't open config file.")
	}
	defer configFile.Close()

	var config Configuration
	jsonParser := json.NewDecoder(configFile)
	if err := jsonParser.Decode(&config); err != nil {
		logger.Println(err.Error())
		logger.Fatal("Can't parse config file.")
	}

	return &config
}

// Setup all callbacks for given connection.
func setupCallbacks(conn *irc.Connection, config *NetworkConfigAll) {
	NetworkName := config.Network.Name

	// join desired irc channel on connection success
	conn.AddCallback("001", func(e *irc.Event) {
		logger.Printf("[%s] // join -> %s\n", NetworkName, config.Network.Channel)
		conn.Join(config.Network.Channel)
	})

	// select next nick if current one is already in use
	nickIndex := 0
	conn.AddCallback("433", func(e *irc.Event) {
		if nickIndex < len(config.Nicks)-1 {
			nickIndex++
		} else {
			nickIndex = 0
		}
		logger.Printf("[%s] // 433 trying change nick to %s\n", NetworkName,
			config.Nicks[nickIndex])
		conn.Nick(config.Nicks[nickIndex])
	})

	// register all callbacks for events to retransmit
	for _, eventcode := range config.Forward {
		conn.AddCallback(eventcode, makeEventHandler(conn, eventcode, config))
	}
}

// return function that handles IRC event with given code
func makeEventHandler(conn *irc.Connection, eventcode string,
	config *NetworkConfigAll) func(*irc.Event) {
	return func(event *irc.Event) {
		// ignore events sent by myselfe
		if event.Nick == conn.GetNick() {
			return
		}

		message := Message{
			Network:   config.Network.Name,
			Eventcode: eventcode,
			Event:     event,
		}
		template := getTemplateForEventcode(eventcode, config.Templates)
		logger.Println(formatMessage(template, &message))
		config.Sink <- message
	}
}

// Convert given message to string usign given template.
func formatMessage(template *template.Template, message *Message) string {
	var buffer bytes.Buffer

	err := template.Execute(&buffer, message)
	if err != nil {
		logger.Fatalf("Invalid template: %s\n", err)
	}

	return buffer.String()
}

// Returns *template.Template from 'templates' that corresponds to 'code' or
// "default" pointer.
func getTemplateForEventcode(code string, templates *TemplateMap) *template.Template {
	template, ok := (*templates)[code]
	if !ok {
		template = (*templates)["default"]
	}
	return template
}

// Connect to single Network and join desired channel.
func makeConnection(config *NetworkConfigAll) {
	logger.Printf("[%s] (%s/%s)\n", config.Network.Name, config.Network.Address,
		config.Network.Channel)

	conn := irc.IRC(config.Nicks[0], config.Username)
	//conn.VerboseCallbackHandler = true
	conn.Debug = true

	err := conn.Connect(config.Network.Address)
	if err != nil {
		logger.Println(err.Error())
		logger.Fatalf("Can't connect to network %s.\n", config.Network.Name)
	}

	setupCallbacks(conn, config)

	go loopMessages(conn, config)
	go conn.Loop()
}

// wait for messages on config.Receiver and write to irc channel only
// those that are not recived on this Network/channel
func loopMessages(conn *irc.Connection, config *NetworkConfigAll) {
	for {
		message := <-config.Receiver
		// ignore messages sent by myselfe
		if message.Network == config.Network.Name {
			continue
		}

		// set topic
		if message.Eventcode == "TOPIC" {
			conn.SendRawf("TOPIC %s :%s (%s)\r\n", config.Network.Channel,
				message.Message(), message.Nick)
		}

		template := getTemplateForEventcode(message.Eventcode, config.Templates)
		text := formatMessage(template, &message)

		if len(text) > 0 {
			conn.Privmsg(config.Network.Channel, text)
		}
	}
}

// Initialize all connections. Returns list of channels for all connected
// Network/channel pair.
func makeConnections(config *Configuration, sink chan Message) []chan Message {
	receivers := make([]chan Message, len(config.Networks))
	templates := makeTemplates(config.Templates)

	for i, Network := range config.Networks {
		receiver := make(chan Message)
		NetworkConfig := &NetworkConfigAll{
			Network:   Network,
			Nicks:     config.Nicks,
			Username:  config.Username,
			Sink:      sink,
			Receiver:  receiver,
			Forward:   config.Forward,
			Templates: templates,
		}
		receivers[i] = receiver
		makeConnection(NetworkConfig)
	}
	return receivers
}

// Initialize template.Template object for each template defined in
// configuration.
func makeTemplates(definition map[string]string) *TemplateMap {
	result := make(TemplateMap)

	for key, value := range definition {
		tmpl, err := template.New(key).Parse(value)
		if err != nil {
			logger.Fatalf("Could not create template for '%s': %s\n", key, err)
		}
		result[key] = tmpl
	}

	return &result
}

// Write all messages recived from sink to all receivers.
func loop(sink chan Message, receivers []chan Message) {
	for message := range sink {
		for _, receiver := range receivers {
			receiver <- message
		}
	}
}

func main() {
	if len(os.Args) == 1 {
		logger.Fatal("No config file specified.")
	}

	config := loadConfig(os.Args[1])
	sink := make(chan Message)
	receivers := makeConnections(config, sink)

	loop(sink, receivers)
}
<|endoftext|>


<commit_before>package main

type TestRunner struct {
	config                TestConfig
	tasks                 []TaskConfig
	globalTaskEnvironment TaskEnvironment
	runtime               Runtime
	steps                 []Step
	report                *TestReport
}

func NewTestRunner(config TestConfig, tasks Tasks, globalTaskEnvironment TaskEnvironment) *TestRunner {
	return &TestRunner{
		config:                config,
		globalTaskEnvironment: globalTaskEnvironment,
		steps: initSteps(config.Spec, tasks, globalTaskEnvironment),
	}
}

func (tr *TestRunner) RunTest(runtime Runtime, out TestOutput) *TestReport {
	tr.runtime = runtime
	systemOutputFetcher := func() ([]byte, error) {
		return runtime.SystemOutput()
	}
	tr.report = NewTestReport(tr.config.Name, systemOutputFetcher, out)

	tr.report.StartingRuntime()
	err := runtime.StartAll()
	if err != nil {
		tr.report.ErrorStartingRuntime(err)
		return tr.report
	}

	for _, step := range tr.steps {
		tr.report.ExecutingStep(step)
		err = step.Execute(runtime, out)
		if err != nil {
			tr.report.StepExecutionFailed(step, err)
			break
		}
	}

	tr.report.StoppingRuntime()
	err = runtime.StopAll()
	if err != nil {
		tr.report.ErrorStoppingRuntime(err)
	}
	return tr.report
}

func (tr *TestRunner) Cleanup() error {
	return tr.runtime.Cleanup()
}

func initSteps(stepConfigs []StepConfig, tasks Tasks, env TaskEnvironment) []Step {
	steps := []Step{}
	for _, stepConfig := range stepConfigs {
		if len(stepConfig.Start) != 0 {
			steps = append(steps, NewStartStep(stepConfig))
		}
		if len(stepConfig.Stop) != 0 {
			steps = append(steps, NewStopStep(stepConfig))
		}
		if len(stepConfig.Wait) != 0 {
			for _, taskName := range stepConfig.Wait {
				steps = append(steps, NewWaitStep(tasks[taskName], env))
			}
		}
		if len(stepConfig.Assert) != 0 {
			for _, taskName := range stepConfig.Assert {
				steps = append(steps, NewAssertStep(tasks[taskName], env))
			}
		}
		if len(stepConfig.Fail) != 0 {
			for _, taskName := range stepConfig.Fail {
				steps = append(steps, NewFailStep(tasks[taskName], env))
			}
		}
	}
	return steps
}
<commit_msg>Cleanup old containers before running test<commit_after>package main

type TestRunner struct {
	config                TestConfig
	tasks                 []TaskConfig
	globalTaskEnvironment TaskEnvironment
	runtime               Runtime
	steps                 []Step
	report                *TestReport
}

func NewTestRunner(config TestConfig, tasks Tasks, globalTaskEnvironment TaskEnvironment) *TestRunner {
	return &TestRunner{
		config:                config,
		globalTaskEnvironment: globalTaskEnvironment,
		steps: initSteps(config.Spec, tasks, globalTaskEnvironment),
	}
}

func (tr *TestRunner) RunTest(runtime Runtime, out TestOutput) *TestReport {
	tr.runtime = runtime
	systemOutputFetcher := func() ([]byte, error) {
		return runtime.SystemOutput()
	}
	tr.report = NewTestReport(tr.config.Name, systemOutputFetcher, out)

	tr.runtime.Cleanup()
	tr.report.StartingRuntime()
	err := runtime.StartAll()
	if err != nil {
		tr.report.ErrorStartingRuntime(err)
		return tr.report
	}

	for _, step := range tr.steps {
		tr.report.ExecutingStep(step)
		err = step.Execute(runtime, out)
		if err != nil {
			tr.report.StepExecutionFailed(step, err)
			break
		}
	}

	tr.report.StoppingRuntime()
	err = runtime.StopAll()
	if err != nil {
		tr.report.ErrorStoppingRuntime(err)
	}
	return tr.report
}

func (tr *TestRunner) Cleanup() error {
	return tr.runtime.Cleanup()
}

func initSteps(stepConfigs []StepConfig, tasks Tasks, env TaskEnvironment) []Step {
	steps := []Step{}
	for _, stepConfig := range stepConfigs {
		if len(stepConfig.Start) != 0 {
			steps = append(steps, NewStartStep(stepConfig))
		}
		if len(stepConfig.Stop) != 0 {
			steps = append(steps, NewStopStep(stepConfig))
		}
		if len(stepConfig.Wait) != 0 {
			for _, taskName := range stepConfig.Wait {
				steps = append(steps, NewWaitStep(tasks[taskName], env))
			}
		}
		if len(stepConfig.Assert) != 0 {
			for _, taskName := range stepConfig.Assert {
				steps = append(steps, NewAssertStep(tasks[taskName], env))
			}
		}
		if len(stepConfig.Fail) != 0 {
			for _, taskName := range stepConfig.Fail {
				steps = append(steps, NewFailStep(tasks[taskName], env))
			}
		}
	}
	return steps
}
<|endoftext|>


<commit_before>//
//  trayicon.go
//
//  Created by Martino Facchin
//  Copyright (c) 2015 Arduino LLC
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//

// +build !cli

package main

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

	log "github.com/Sirupsen/logrus"
	"github.com/arduino/arduino-create-agent/icon"
	"github.com/getlantern/systray"
	"github.com/go-ini/ini"
	"github.com/kardianos/osext"
	"github.com/skratchdot/open-golang/open"
	"github.com/vharitonsky/iniflags"
	"go.bug.st/serial.v1"
)

func setupSysTray() {
	runtime.LockOSThread()
	if *hibernate == true {
		systray.Run(setupSysTrayHibernate)
	} else {
		systray.Run(setupSysTrayReal)
	}
}

func addRebootTrayElement() {
	reboot_tray := systray.AddMenuItem("Reboot to update", "")

	go func() {
		<-reboot_tray.ClickedCh
		systray.Quit()
		log.Println("Restarting now...")
		log.Println("Restart because addReebotTrayElement")
		restart("")
	}()
}

type ConfigIni struct {
	Name      string
	Localtion string
}

func getConfigs() []ConfigIni {
	// parse all configs in executable folder
	// config.ini must be there, so call it Default
	src, _ := osext.Executable()
	dest := filepath.Dir(src)

	var configs []ConfigIni

	filepath.Walk(dest, func(path string, f os.FileInfo, _ error) error {
		if !f.IsDir() {
			if filepath.Ext(path) == ".ini" {
				cfg, _ := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, filepath.Join(dest, f.Name()))
				defaultSection, err := cfg.GetSection("")
				name := defaultSection.Key("name").String()
				if name == "" || err != nil {
					name = "Default config"
				}
				conf := ConfigIni{Name: name, Localtion: f.Name()}
				configs = append(configs, conf)
			}
		}
		return nil
	})
	return configs
}

func applyEnvironment(filename string) {
	src, _ := osext.Executable()
	dest := filepath.Dir(src)
	cfg, _ := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, filepath.Join(dest, filename))
	defaultSection, err := cfg.GetSection("env")
	if err != nil {
		return
	}
	for _, env := range defaultSection.KeyStrings() {
		val := defaultSection.Key(env).String()
		log.Info("Applying env setting: " + env + "=" + val)
		os.Setenv(env, val)
	}
}

func setupSysTrayReal() {

	systray.SetIcon(icon.GetIcon())
	mUrl := systray.AddMenuItem("Go to Arduino Create", "Arduino Create")
	mDebug := systray.AddMenuItem("Open debug console", "Debug console")
	menuVer := systray.AddMenuItem("Agent version "+version+"-"+git_revision, "")
	mPause := systray.AddMenuItem("Pause Plugin", "")
	var mConfigCheckbox []*systray.MenuItem

	configs := getConfigs()

	for _, config := range configs {
		entry := systray.AddMenuItem(config.Name, "")
		mConfigCheckbox = append(mConfigCheckbox, entry)
		// decorate configs
		gliph := " ☐ "
		if *configIni == config.Localtion {
			gliph = " 🗹 "
		}
		entry.SetTitle(gliph + config.Name)
	}
	//mQuit := systray.AddMenuItem("Quit Plugin", "")

	menuVer.Disable()

	for i, _ := range mConfigCheckbox {
		go func(v int) {
			for {
				<-mConfigCheckbox[v].ClickedCh
				flag.Set("config", configs[v].Localtion)
				iniflags.UpdateConfig()
				applyEnvironment(configs[v].Localtion)
				mConfigCheckbox[v].SetTitle(" 🗹 " + configs[v].Name)
				//mConfigCheckbox[v].Check()
				for j, _ := range mConfigCheckbox {
					if j != v {
						mConfigCheckbox[j].SetTitle(" ☐ " + configs[j].Name)
					}
				}
			}
		}(i)
	}

	go func() {
		<-mPause.ClickedCh
		ports, _ := serial.GetPortsList()
		for _, element := range ports {
			spClose(element)
		}
		systray.Quit()
		*hibernate = true
		log.Println("Restart becayse setup went wrong?")
		restart("")
	}()

	// go func() {
	// 	<-mQuit.ClickedCh
	// 	systray.Quit()
	// 	exit()
	// }()

	go func() {
		for {
			<-mDebug.ClickedCh
			logAction("log on")
			open.Start("http://localhost" + port)
		}
	}()

	// We can manipulate the systray in other goroutines
	go func() {
		for {
			<-mUrl.ClickedCh
			open.Start("http://create.arduino.cc")
		}
	}()
}

func setupSysTrayHibernate() {

	systray.SetIcon(icon.GetIconHiber())
	mOpen := systray.AddMenuItem("Open Plugin", "")
	mQuit := systray.AddMenuItem("Kill Plugin", "")

	go func() {
		<-mOpen.ClickedCh
		*hibernate = false
		log.Println("Restart for hubernation")
		systray.Quit()
		restart("")
	}()

	go func() {
		<-mQuit.ClickedCh
		systray.Quit()
		exit()
	}()
}

func quitSysTray() {
	systray.Quit()
}
<commit_msg>Show additional configs only if there is more than one<commit_after>//
//  trayicon.go
//
//  Created by Martino Facchin
//  Copyright (c) 2015 Arduino LLC
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//

// +build !cli

package main

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

	log "github.com/Sirupsen/logrus"
	"github.com/arduino/arduino-create-agent/icon"
	"github.com/getlantern/systray"
	"github.com/go-ini/ini"
	"github.com/kardianos/osext"
	"github.com/skratchdot/open-golang/open"
	"github.com/vharitonsky/iniflags"
	"go.bug.st/serial.v1"
)

func setupSysTray() {
	runtime.LockOSThread()
	if *hibernate == true {
		systray.Run(setupSysTrayHibernate)
	} else {
		systray.Run(setupSysTrayReal)
	}
}

func addRebootTrayElement() {
	reboot_tray := systray.AddMenuItem("Reboot to update", "")

	go func() {
		<-reboot_tray.ClickedCh
		systray.Quit()
		log.Println("Restarting now...")
		log.Println("Restart because addReebotTrayElement")
		restart("")
	}()
}

type ConfigIni struct {
	Name      string
	Localtion string
}

func getConfigs() []ConfigIni {
	// parse all configs in executable folder
	// config.ini must be there, so call it Default
	src, _ := osext.Executable()
	dest := filepath.Dir(src)

	var configs []ConfigIni

	filepath.Walk(dest, func(path string, f os.FileInfo, _ error) error {
		if !f.IsDir() {
			if filepath.Ext(path) == ".ini" {
				cfg, _ := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, filepath.Join(dest, f.Name()))
				defaultSection, err := cfg.GetSection("")
				name := defaultSection.Key("name").String()
				if name == "" || err != nil {
					name = "Default config"
				}
				conf := ConfigIni{Name: name, Localtion: f.Name()}
				configs = append(configs, conf)
			}
		}
		return nil
	})
	return configs
}

func applyEnvironment(filename string) {
	src, _ := osext.Executable()
	dest := filepath.Dir(src)
	cfg, _ := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, filepath.Join(dest, filename))
	defaultSection, err := cfg.GetSection("env")
	if err != nil {
		return
	}
	for _, env := range defaultSection.KeyStrings() {
		val := defaultSection.Key(env).String()
		log.Info("Applying env setting: " + env + "=" + val)
		os.Setenv(env, val)
	}
}

func setupSysTrayReal() {

	systray.SetIcon(icon.GetIcon())
	mUrl := systray.AddMenuItem("Go to Arduino Create", "Arduino Create")
	mDebug := systray.AddMenuItem("Open debug console", "Debug console")
	menuVer := systray.AddMenuItem("Agent version "+version+"-"+git_revision, "")
	mPause := systray.AddMenuItem("Pause Plugin", "")
	var mConfigCheckbox []*systray.MenuItem

	configs := getConfigs()

	if len(configs) > 1 {
		for _, config := range configs {
			entry := systray.AddMenuItem(config.Name, "")
			mConfigCheckbox = append(mConfigCheckbox, entry)
			// decorate configs
			gliph := " ☐ "
			if *configIni == config.Localtion {
				gliph = " 🗹 "
			}
			entry.SetTitle(gliph + config.Name)
		}
	} else {
		// apply env setting from first config immediately
		// applyEnvironment(configs[0].Localtion)
	}
	//mQuit := systray.AddMenuItem("Quit Plugin", "")

	menuVer.Disable()

	for i, _ := range mConfigCheckbox {
		go func(v int) {
			for {
				<-mConfigCheckbox[v].ClickedCh
				flag.Set("config", configs[v].Localtion)
				iniflags.UpdateConfig()
				applyEnvironment(configs[v].Localtion)
				mConfigCheckbox[v].SetTitle(" 🗹 " + configs[v].Name)
				//mConfigCheckbox[v].Check()
				for j, _ := range mConfigCheckbox {
					if j != v {
						mConfigCheckbox[j].SetTitle(" ☐ " + configs[j].Name)
					}
				}
			}
		}(i)
	}

	go func() {
		<-mPause.ClickedCh
		ports, _ := serial.GetPortsList()
		for _, element := range ports {
			spClose(element)
		}
		systray.Quit()
		*hibernate = true
		log.Println("Restart becayse setup went wrong?")
		restart("")
	}()

	// go func() {
	// 	<-mQuit.ClickedCh
	// 	systray.Quit()
	// 	exit()
	// }()

	go func() {
		for {
			<-mDebug.ClickedCh
			logAction("log on")
			open.Start("http://localhost" + port)
		}
	}()

	// We can manipulate the systray in other goroutines
	go func() {
		for {
			<-mUrl.ClickedCh
			open.Start("http://create.arduino.cc")
		}
	}()
}

func setupSysTrayHibernate() {

	systray.SetIcon(icon.GetIconHiber())
	mOpen := systray.AddMenuItem("Open Plugin", "")
	mQuit := systray.AddMenuItem("Kill Plugin", "")

	go func() {
		<-mOpen.ClickedCh
		*hibernate = false
		log.Println("Restart for hubernation")
		systray.Quit()
		restart("")
	}()

	go func() {
		<-mQuit.ClickedCh
		systray.Quit()
		exit()
	}()
}

func quitSysTray() {
	systray.Quit()
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 mutable

import (
	"fmt"
	"math"
	"os"
	"time"

	"github.com/jacobsa/timeutil"
	"golang.org/x/net/context"
)

// A mutable view on some content. Created with an initial read-only view,
// which then can be modified by the user and read back. Keeps track of which
// portion of the content has been dirtied.
//
// External synchronization is required.
type Content interface {
	// Panic if any internal invariants are violated.
	CheckInvariants()

	// Destroy any state used by the object, putting it into an indeterminate
	// state. The object must not be used again.
	Destroy()

	// Read part of the content, with semantics equivalent to io.ReaderAt aside
	// from context support.
	ReadAt(ctx context.Context, buf []byte, offset int64) (n int, err error)

	// Return information about the current state of the content.
	Stat(ctx context.Context) (sr StatResult, err error)

	// Write into the content, with semantics equivalent to io.WriterAt aside from
	// context support.
	WriteAt(ctx context.Context, buf []byte, offset int64) (n int, err error)

	// Truncate our the content to the given number of bytes, extending if n is
	// greater than the current size.
	Truncate(ctx context.Context, n int64) (err error)
}

type StatResult struct {
	// The current size in bytes of the content.
	Size int64

	// It is guaranteed that all bytes in the range [0, DirtyThreshold) are
	// unmodified from the original content with which the mutable content object
	// was created.
	DirtyThreshold int64

	// The time at which the content was last updated, or nil if we've never
	// changed it.
	Mtime *time.Time
}

// Create a mutable content object whose initial contents are given by the
// supplied file. The caller must ensure that the file is not used further by
// anybody else.
func NewContent(
	initialContent *os.File,
	clock timeutil.Clock) (mc Content, err error) {
	// Find the file's size.
	size, err := initialContent.Seek(0, 2)
	if err != nil {
		err = fmt.Errorf("Seek: %v", err)
		return
	}

	// Create the Content.
	mc = &mutableContent{
		clock:          clock,
		contents:       initialContent,
		dirtyThreshold: size,
	}

	return
}

type mutableContent struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	clock timeutil.Clock

	/////////////////////////
	// Mutable state
	/////////////////////////

	destroyed bool

	// Have we been dirtied since we were created?
	dirty bool

	// A file containing our current contents.
	contents *os.File

	// The lowest byte index that has been modified from the initial contents.
	//
	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	dirtyThreshold int64

	// The time at which a method that modifies our contents was last called, or
	// nil if never.
	//
	// INVARIANT: dirty => mtime != nil
	mtime *time.Time
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (mc *mutableContent) CheckInvariants() {
	if mc.destroyed {
		panic("Use of destroyed mutableContent object.")
	}

	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	if !mc.dirty {
		sr, err := mc.Stat(context.Background())
		if err != nil {
			panic(fmt.Sprintf("Stat: %v", err))
		}

		if sr.DirtyThreshold != sr.Size {
			panic(fmt.Sprintf("Mismatch: %d vs. %d", sr.DirtyThreshold, sr.Size))
		}
	}

	// INVARIANT: dirty => mtime != nil
	if mc.dirty && mc.mtime == nil {
		panic("Expected a non-nil mtime")
	}
}

func (mc *mutableContent) Destroy() {
	mc.destroyed = true

	// Throw away the file.
	mc.contents.Close()
	mc.contents = nil
}

func (mc *mutableContent) ReadAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	n, err = mc.contents.ReadAt(buf, offset)
	return
}

func (mc *mutableContent) Stat(
	ctx context.Context) (sr StatResult, err error) {
	sr.DirtyThreshold = mc.dirtyThreshold
	sr.Mtime = mc.mtime

	// Get the size from the file.
	sr.Size, err = mc.contents.Seek(0, 2)
	if err != nil {
		err = fmt.Errorf("Seek: %v", err)
		return
	}

	return
}

func (mc *mutableContent) WriteAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, offset)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	n, err = mc.contents.WriteAt(buf, offset)

	return
}

func (mc *mutableContent) Truncate(
	ctx context.Context,
	n int64) (err error) {
	// Make sure we have a read/write lease.
	if err = mc.ensureReadWriteLease(ctx); err != nil {
		err = fmt.Errorf("ensureReadWriteLease: %v", err)
		return
	}

	// Convert to signed, which is what lease.ReadWriteLease wants.
	if n > math.MaxInt64 {
		err = fmt.Errorf("Illegal offset: %v", n)
		return
	}

	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, n)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	err = mc.readWriteLease.Truncate(int64(n))

	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func minInt64(a int64, b int64) int64 {
	if a < b {
		return a
	}

	return b
}

// Ensure that mc.readWriteLease is non-nil with an authoritative view of mc's
// contents.
func (mc *mutableContent) ensureReadWriteLease(
	ctx context.Context) (err error) {
	// Is there anything to do?
	if mc.readWriteLease != nil {
		return
	}

	// Set up the read/write lease.
	rwl, err := mc.initialContent.Upgrade(ctx)
	if err != nil {
		err = fmt.Errorf("initialContent.Upgrade: %v", err)
		return
	}

	mc.readWriteLease = rwl
	mc.initialContent = nil

	return
}
<commit_msg>mutableContent.Truncate<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 mutable

import (
	"fmt"
	"os"
	"time"

	"github.com/jacobsa/timeutil"
	"golang.org/x/net/context"
)

// A mutable view on some content. Created with an initial read-only view,
// which then can be modified by the user and read back. Keeps track of which
// portion of the content has been dirtied.
//
// External synchronization is required.
type Content interface {
	// Panic if any internal invariants are violated.
	CheckInvariants()

	// Destroy any state used by the object, putting it into an indeterminate
	// state. The object must not be used again.
	Destroy()

	// Read part of the content, with semantics equivalent to io.ReaderAt aside
	// from context support.
	ReadAt(ctx context.Context, buf []byte, offset int64) (n int, err error)

	// Return information about the current state of the content.
	Stat(ctx context.Context) (sr StatResult, err error)

	// Write into the content, with semantics equivalent to io.WriterAt aside from
	// context support.
	WriteAt(ctx context.Context, buf []byte, offset int64) (n int, err error)

	// Truncate our the content to the given number of bytes, extending if n is
	// greater than the current size.
	Truncate(ctx context.Context, n int64) (err error)
}

type StatResult struct {
	// The current size in bytes of the content.
	Size int64

	// It is guaranteed that all bytes in the range [0, DirtyThreshold) are
	// unmodified from the original content with which the mutable content object
	// was created.
	DirtyThreshold int64

	// The time at which the content was last updated, or nil if we've never
	// changed it.
	Mtime *time.Time
}

// Create a mutable content object whose initial contents are given by the
// supplied file. The caller must ensure that the file is not used further by
// anybody else.
func NewContent(
	initialContent *os.File,
	clock timeutil.Clock) (mc Content, err error) {
	// Find the file's size.
	size, err := initialContent.Seek(0, 2)
	if err != nil {
		err = fmt.Errorf("Seek: %v", err)
		return
	}

	// Create the Content.
	mc = &mutableContent{
		clock:          clock,
		contents:       initialContent,
		dirtyThreshold: size,
	}

	return
}

type mutableContent struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	clock timeutil.Clock

	/////////////////////////
	// Mutable state
	/////////////////////////

	destroyed bool

	// Have we been dirtied since we were created?
	dirty bool

	// A file containing our current contents.
	contents *os.File

	// The lowest byte index that has been modified from the initial contents.
	//
	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	dirtyThreshold int64

	// The time at which a method that modifies our contents was last called, or
	// nil if never.
	//
	// INVARIANT: dirty => mtime != nil
	mtime *time.Time
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (mc *mutableContent) CheckInvariants() {
	if mc.destroyed {
		panic("Use of destroyed mutableContent object.")
	}

	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	if !mc.dirty {
		sr, err := mc.Stat(context.Background())
		if err != nil {
			panic(fmt.Sprintf("Stat: %v", err))
		}

		if sr.DirtyThreshold != sr.Size {
			panic(fmt.Sprintf("Mismatch: %d vs. %d", sr.DirtyThreshold, sr.Size))
		}
	}

	// INVARIANT: dirty => mtime != nil
	if mc.dirty && mc.mtime == nil {
		panic("Expected a non-nil mtime")
	}
}

func (mc *mutableContent) Destroy() {
	mc.destroyed = true

	// Throw away the file.
	mc.contents.Close()
	mc.contents = nil
}

func (mc *mutableContent) ReadAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	n, err = mc.contents.ReadAt(buf, offset)
	return
}

func (mc *mutableContent) Stat(
	ctx context.Context) (sr StatResult, err error) {
	sr.DirtyThreshold = mc.dirtyThreshold
	sr.Mtime = mc.mtime

	// Get the size from the file.
	sr.Size, err = mc.contents.Seek(0, 2)
	if err != nil {
		err = fmt.Errorf("Seek: %v", err)
		return
	}

	return
}

func (mc *mutableContent) WriteAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, offset)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	n, err = mc.contents.WriteAt(buf, offset)

	return
}

func (mc *mutableContent) Truncate(
	ctx context.Context,
	n int64) (err error) {
	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, n)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	err = mc.contents.Truncate(int64(n))

	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func minInt64(a int64, b int64) int64 {
	if a < b {
		return a
	}

	return b
}

// Ensure that mc.readWriteLease is non-nil with an authoritative view of mc's
// contents.
func (mc *mutableContent) ensureReadWriteLease(
	ctx context.Context) (err error) {
	// Is there anything to do?
	if mc.readWriteLease != nil {
		return
	}

	// Set up the read/write lease.
	rwl, err := mc.initialContent.Upgrade(ctx)
	if err != nil {
		err = fmt.Errorf("initialContent.Upgrade: %v", err)
		return
	}

	mc.readWriteLease = rwl
	mc.initialContent = nil

	return
}
<|endoftext|>


<commit_before>// Copyright 2017-2018 Authors of Cilium
//
// 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 helpers

import (
	"bytes"
	"fmt"
	"os"
	"time"

	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
	ginkgoext "github.com/cilium/cilium/test/ginkgo-ext"
)

var (
	// HelperTimeout is a predefined timeout value for commands.
	HelperTimeout time.Duration = 300 // WithTimeout helper translates it to seconds

	// BasePath is the path in the Vagrant VMs to which the test directory
	// is mounted
	BasePath = "/home/vagrant/go/src/github.com/cilium/cilium/test"
	// CheckLogs newtes a new buffer where all the warnings and checks that
	// happens during the test are saved. This buffer will be printed in the
	// test output inside <checks> labels.
	CheckLogs = ginkgoext.NewWriter(new(bytes.Buffer))
)

const (

	//CiliumPath is the path where cilium test code is located.
	CiliumPath = "/src/github.com/cilium/cilium/test"

	// K8sManifestBase tells ginkgo suite where to look for manifests
	K8sManifestBase = "k8sT/manifests"

	// VM / Test suite constants.
	K8s     = "k8s"
	K8s1    = "k8s1"
	K8s1Ip  = "192.168.36.11"
	K8s2    = "k8s2"
	K8s2Ip  = "192.168.36.12"
	Runtime = "runtime"

	Enabled  = "enabled"
	Disabled = "disabled"
	Total    = "total"
	Public   = "public"
	Private  = "private"
	Name     = "Name"

	// PolicyEnforcement represents the PolicyEnforcement configuration option
	// for the Cilium agent.
	PolicyEnforcement = "PolicyEnforcement"

	// PolicyEnforcementDefault represents the default PolicyEnforcement mode
	// for Cilium.
	PolicyEnforcementDefault = "default"

	// PolicyEnforcementAlways represents the PolicyEnforcement mode
	// for Cilium in which traffic is denied by default even when no policy
	// is imported.
	PolicyEnforcementAlways = "always"

	// PolicyEnforcementNever represents the PolicyEnforcement mode
	// for Cilium in which traffic is always allowed even if there is a policy
	// selecting endpoints.
	PolicyEnforcementNever = "never"

	// Docker Image names

	// CiliumDockerNetwork is the name of the Docker network which Cilium manages.
	CiliumDockerNetwork = "cilium-net"

	// HostDockerNetwork is the name of the host network driver.
	HostDockerNetwork = "host"

	// NetperfImage is the Docker image used for performance testing
	NetperfImage = "docker.io/tgraf/netperf:latest"

	// HttpdImage is the image used for starting an HTTP server.
	HttpdImage = "docker.io/cilium/demo-httpd:latest"

	// Names of commonly used containers in tests.
	Httpd1 = "httpd1"
	Httpd2 = "httpd2"
	Httpd3 = "httpd3"
	App1   = "app1"
	App2   = "app2"
	App3   = "app3"
	Client = "client"
	Server = "server"
	Host   = "host"
	// Container lifecycle actions.
	Create = "create"
	Delete = "delete"

	// IP Address families.
	IPv4 = "IPv4"
	IPv6 = "IPv6"

	// LogPerm is the permission for files that are created by this framework
	// that contain logs, outputs of Cilium CLI commands, etc.
	LogPerm = os.FileMode(0666)

	// Configuration options for endpoints. Copied from endpoint/endpoint.go
	// TODO: these should be converted into types for use in configuration
	// functions instead of using basic strings.

	OptionConntrackAccounting = "ConntrackAccounting"
	OptionConntrackLocal      = "ConntrackLocal"
	OptionConntrack           = "Conntrack"
	OptionDebug               = "Debug"
	OptionDropNotify          = "DropNotification"
	OptionTraceNotify         = "TraceNotification"
	OptionNAT46               = "NAT46"
	OptionIngressPolicy       = "IngressPolicy"
	OptionEgressPolicy        = "EgressPolicy"
	OptionIngress             = "ingress"
	OptionEgress              = "egress"
	OptionNone                = "none"
	OptionDisabled            = "Disabled"
	OptionEnabled             = "Enabled"

	StateTerminating = "Terminating"
	StateRunning     = "Running"

	PingCount          = 5
	CurlConnectTimeout = 3

	DefaultNamespace    = "default"
	KubeSystemNamespace = "kube-system"

	TestResultsPath = "test_results/"
	RunDir          = "/var/run/cilium"
	LibDir          = "/var/lib/cilium"

	DaemonName             = "cilium"
	CiliumBugtool          = "cilium-bugtool"
	CiliumDockerDaemonName = "cilium-docker"
	AgentDaemon            = "cilium-agent"

	GeneratedHTMLManifest   = "html.yaml"
	GeneratedServerManifest = "server.yaml"
	GeneratedClientManifest = "client.yaml"

	KubectlCreate = ResourceLifeCycleAction("create")
	KubectlDelete = ResourceLifeCycleAction("delete")
	KubectlApply  = ResourceLifeCycleAction("apply")

	KubectlPolicyNameLabel      = k8sConst.PolicyLabelName
	KubectlPolicyNameSpaceLabel = k8sConst.PolicyLabelNamespace

	StableImage = "docker.io/cilium/cilium:v1.1.1"

	configMap = "ConfigMap"
	daemonSet = "DaemonSet"

	MonitorLogFileName = "monitor.log"
	microscopeManifest = "microscope.yaml"

	// CiliumTestLog is the filename where the cilium logs that happens during
	// the test are saved.
	CiliumTestLog = "cilium-test.log"

	// IPv4Host is an IP which is used in some datapath tests for simulating external IPv4 connectivity.
	IPv4Host = "192.168.254.254"

	// IPv6Host is an IP which is used in some datapath tests for simulating external IPv6 connectivity.
	IPv6Host = "fdff::ff"

	// Logs messages that should not be in the cilium logs.
	panicMessage      = "panic:"
	deadLockHeader    = "POTENTIAL DEADLOCK:"               // from github.com/sasha-s/go-deadlock/deadlock.go:header
	segmentationFault = "segmentation fault"                // from https://github.com/cilium/cilium/issues/3233
	NACKreceived      = "NACK received for version"         // from https://github.com/cilium/cilium/issues/4003
	RunInitFailed     = "RunInit: Command execution failed" // from https://github.com/cilium/cilium/pull/5052

	contextDeadlineExceeded = "context deadline exceeded"
	ErrorLogs               = "level=error"
	WarningLogs             = "level=warning"
	APIPanicked             = "Cilium API handler panicked"
)

// Re-definitions of stable constants in the API. The re-definition is on
// purpose to validate these values in the API. They may never change
const (
	// ReservedIdentityHealth is equivalent to pkg/identity.ReservedIdentityHealth
	ReservedIdentityHealth = 4
)

// NightlyStableUpgradesFrom the cilium images to update from in Nightly test.
var NightlyStableUpgradesFrom = []string{"docker.io/cilium/cilium:v1.0.5"}

// CiliumDSPath is the default Cilium DaemonSet path to use in all test.
var CiliumDSPath = "cilium_ds.jsonnet"

var checkLogsMessages = []string{panicMessage, deadLockHeader, segmentationFault, NACKreceived, RunInitFailed}
var countLogsMessages = []string{contextDeadlineExceeded, ErrorLogs, WarningLogs, APIPanicked}

var ciliumCLICommands = map[string]string{
	"cilium endpoint list -o json":          "endpoint_list.txt",
	"cilium service list -o json":           "service_list.txt",
	"cilium config":                         "config.txt",
	"sudo cilium bpf lb list":               "bpf_lb_list.txt",
	"sudo cilium bpf ct list global":        "bpf_ct_list.txt",
	"sudo cilium bpf tunnel list":           "bpf_tunnel_list.txt",
	"cilium policy get":                     "policy_get.txt",
	"cilium status --all-controllers":       "status.txt",
	"cilium kvstore get cilium --recursive": "kvstore_get.txt",
}

// ciliumKubCLICommands these commands are the same as `ciliumCLICommands` but
// it'll run inside a container and it does not have sudo support
var ciliumKubCLICommands = map[string]string{
	"cilium endpoint list -o json":          "endpoint_list.txt",
	"cilium service list -o json":           "service_list.txt",
	"cilium config":                         "config.txt",
	"cilium bpf lb list":                    "bpf_lb_list.txt",
	"cilium bpf ct list global":             "bpf_ct_list.txt",
	"cilium bpf tunnel list":                "bpf_tunnel_list.txt",
	"cilium policy get":                     "policy_get.txt",
	"cilium status --all-controllers":       "status.txt",
	"cilium kvstore get cilium --recursive": "kvstore_get.txt",
}

//GetFilePath returns the absolute path of the provided filename
func GetFilePath(filename string) string {
	return fmt.Sprintf("%s/%s", BasePath, filename)
}

// K8s1VMName is the name of the Kubernetes master node when running K8s tests.
func K8s1VMName() string {
	return fmt.Sprintf("k8s1-%s", GetCurrentK8SEnv())
}

// K8s2VMName is the name of the Kubernetes worker node when running K8s tests.
func K8s2VMName() string {
	return fmt.Sprintf("k8s2-%s", GetCurrentK8SEnv())
}
<commit_msg>test: use tgraf/netperf:v1.0 image<commit_after>// Copyright 2017-2018 Authors of Cilium
//
// 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 helpers

import (
	"bytes"
	"fmt"
	"os"
	"time"

	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
	ginkgoext "github.com/cilium/cilium/test/ginkgo-ext"
)

var (
	// HelperTimeout is a predefined timeout value for commands.
	HelperTimeout time.Duration = 300 // WithTimeout helper translates it to seconds

	// BasePath is the path in the Vagrant VMs to which the test directory
	// is mounted
	BasePath = "/home/vagrant/go/src/github.com/cilium/cilium/test"
	// CheckLogs newtes a new buffer where all the warnings and checks that
	// happens during the test are saved. This buffer will be printed in the
	// test output inside <checks> labels.
	CheckLogs = ginkgoext.NewWriter(new(bytes.Buffer))
)

const (

	//CiliumPath is the path where cilium test code is located.
	CiliumPath = "/src/github.com/cilium/cilium/test"

	// K8sManifestBase tells ginkgo suite where to look for manifests
	K8sManifestBase = "k8sT/manifests"

	// VM / Test suite constants.
	K8s     = "k8s"
	K8s1    = "k8s1"
	K8s1Ip  = "192.168.36.11"
	K8s2    = "k8s2"
	K8s2Ip  = "192.168.36.12"
	Runtime = "runtime"

	Enabled  = "enabled"
	Disabled = "disabled"
	Total    = "total"
	Public   = "public"
	Private  = "private"
	Name     = "Name"

	// PolicyEnforcement represents the PolicyEnforcement configuration option
	// for the Cilium agent.
	PolicyEnforcement = "PolicyEnforcement"

	// PolicyEnforcementDefault represents the default PolicyEnforcement mode
	// for Cilium.
	PolicyEnforcementDefault = "default"

	// PolicyEnforcementAlways represents the PolicyEnforcement mode
	// for Cilium in which traffic is denied by default even when no policy
	// is imported.
	PolicyEnforcementAlways = "always"

	// PolicyEnforcementNever represents the PolicyEnforcement mode
	// for Cilium in which traffic is always allowed even if there is a policy
	// selecting endpoints.
	PolicyEnforcementNever = "never"

	// Docker Image names

	// CiliumDockerNetwork is the name of the Docker network which Cilium manages.
	CiliumDockerNetwork = "cilium-net"

	// HostDockerNetwork is the name of the host network driver.
	HostDockerNetwork = "host"

	// NetperfImage is the Docker image used for performance testing
	NetperfImage = "docker.io/tgraf/netperf:v1.0"

	// HttpdImage is the image used for starting an HTTP server.
	HttpdImage = "docker.io/cilium/demo-httpd:latest"

	// Names of commonly used containers in tests.
	Httpd1 = "httpd1"
	Httpd2 = "httpd2"
	Httpd3 = "httpd3"
	App1   = "app1"
	App2   = "app2"
	App3   = "app3"
	Client = "client"
	Server = "server"
	Host   = "host"
	// Container lifecycle actions.
	Create = "create"
	Delete = "delete"

	// IP Address families.
	IPv4 = "IPv4"
	IPv6 = "IPv6"

	// LogPerm is the permission for files that are created by this framework
	// that contain logs, outputs of Cilium CLI commands, etc.
	LogPerm = os.FileMode(0666)

	// Configuration options for endpoints. Copied from endpoint/endpoint.go
	// TODO: these should be converted into types for use in configuration
	// functions instead of using basic strings.

	OptionConntrackAccounting = "ConntrackAccounting"
	OptionConntrackLocal      = "ConntrackLocal"
	OptionConntrack           = "Conntrack"
	OptionDebug               = "Debug"
	OptionDropNotify          = "DropNotification"
	OptionTraceNotify         = "TraceNotification"
	OptionNAT46               = "NAT46"
	OptionIngressPolicy       = "IngressPolicy"
	OptionEgressPolicy        = "EgressPolicy"
	OptionIngress             = "ingress"
	OptionEgress              = "egress"
	OptionNone                = "none"
	OptionDisabled            = "Disabled"
	OptionEnabled             = "Enabled"

	StateTerminating = "Terminating"
	StateRunning     = "Running"

	PingCount          = 5
	CurlConnectTimeout = 3

	DefaultNamespace    = "default"
	KubeSystemNamespace = "kube-system"

	TestResultsPath = "test_results/"
	RunDir          = "/var/run/cilium"
	LibDir          = "/var/lib/cilium"

	DaemonName             = "cilium"
	CiliumBugtool          = "cilium-bugtool"
	CiliumDockerDaemonName = "cilium-docker"
	AgentDaemon            = "cilium-agent"

	GeneratedHTMLManifest   = "html.yaml"
	GeneratedServerManifest = "server.yaml"
	GeneratedClientManifest = "client.yaml"

	KubectlCreate = ResourceLifeCycleAction("create")
	KubectlDelete = ResourceLifeCycleAction("delete")
	KubectlApply  = ResourceLifeCycleAction("apply")

	KubectlPolicyNameLabel      = k8sConst.PolicyLabelName
	KubectlPolicyNameSpaceLabel = k8sConst.PolicyLabelNamespace

	StableImage = "docker.io/cilium/cilium:v1.1.1"

	configMap = "ConfigMap"
	daemonSet = "DaemonSet"

	MonitorLogFileName = "monitor.log"
	microscopeManifest = "microscope.yaml"

	// CiliumTestLog is the filename where the cilium logs that happens during
	// the test are saved.
	CiliumTestLog = "cilium-test.log"

	// IPv4Host is an IP which is used in some datapath tests for simulating external IPv4 connectivity.
	IPv4Host = "192.168.254.254"

	// IPv6Host is an IP which is used in some datapath tests for simulating external IPv6 connectivity.
	IPv6Host = "fdff::ff"

	// Logs messages that should not be in the cilium logs.
	panicMessage      = "panic:"
	deadLockHeader    = "POTENTIAL DEADLOCK:"               // from github.com/sasha-s/go-deadlock/deadlock.go:header
	segmentationFault = "segmentation fault"                // from https://github.com/cilium/cilium/issues/3233
	NACKreceived      = "NACK received for version"         // from https://github.com/cilium/cilium/issues/4003
	RunInitFailed     = "RunInit: Command execution failed" // from https://github.com/cilium/cilium/pull/5052

	contextDeadlineExceeded = "context deadline exceeded"
	ErrorLogs               = "level=error"
	WarningLogs             = "level=warning"
	APIPanicked             = "Cilium API handler panicked"
)

// Re-definitions of stable constants in the API. The re-definition is on
// purpose to validate these values in the API. They may never change
const (
	// ReservedIdentityHealth is equivalent to pkg/identity.ReservedIdentityHealth
	ReservedIdentityHealth = 4
)

// NightlyStableUpgradesFrom the cilium images to update from in Nightly test.
var NightlyStableUpgradesFrom = []string{"docker.io/cilium/cilium:v1.0.5"}

// CiliumDSPath is the default Cilium DaemonSet path to use in all test.
var CiliumDSPath = "cilium_ds.jsonnet"

var checkLogsMessages = []string{panicMessage, deadLockHeader, segmentationFault, NACKreceived, RunInitFailed}
var countLogsMessages = []string{contextDeadlineExceeded, ErrorLogs, WarningLogs, APIPanicked}

var ciliumCLICommands = map[string]string{
	"cilium endpoint list -o json":          "endpoint_list.txt",
	"cilium service list -o json":           "service_list.txt",
	"cilium config":                         "config.txt",
	"sudo cilium bpf lb list":               "bpf_lb_list.txt",
	"sudo cilium bpf ct list global":        "bpf_ct_list.txt",
	"sudo cilium bpf tunnel list":           "bpf_tunnel_list.txt",
	"cilium policy get":                     "policy_get.txt",
	"cilium status --all-controllers":       "status.txt",
	"cilium kvstore get cilium --recursive": "kvstore_get.txt",
}

// ciliumKubCLICommands these commands are the same as `ciliumCLICommands` but
// it'll run inside a container and it does not have sudo support
var ciliumKubCLICommands = map[string]string{
	"cilium endpoint list -o json":          "endpoint_list.txt",
	"cilium service list -o json":           "service_list.txt",
	"cilium config":                         "config.txt",
	"cilium bpf lb list":                    "bpf_lb_list.txt",
	"cilium bpf ct list global":             "bpf_ct_list.txt",
	"cilium bpf tunnel list":                "bpf_tunnel_list.txt",
	"cilium policy get":                     "policy_get.txt",
	"cilium status --all-controllers":       "status.txt",
	"cilium kvstore get cilium --recursive": "kvstore_get.txt",
}

//GetFilePath returns the absolute path of the provided filename
func GetFilePath(filename string) string {
	return fmt.Sprintf("%s/%s", BasePath, filename)
}

// K8s1VMName is the name of the Kubernetes master node when running K8s tests.
func K8s1VMName() string {
	return fmt.Sprintf("k8s1-%s", GetCurrentK8SEnv())
}

// K8s2VMName is the name of the Kubernetes worker node when running K8s tests.
func K8s2VMName() string {
	return fmt.Sprintf("k8s2-%s", GetCurrentK8SEnv())
}
<|endoftext|>


<commit_before>/*
   Copyright 2015 The Httpgzip Authors. See the AUTHORS file at the
   top-level directory of this distribution and at
   <https://xi2.org/x/httpgzip/m/AUTHORS>.

   This file is part of Httpgzip.

   Httpgzip is free software: you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Httpgzip is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Httpgzip.  If not, see <https://www.gnu.org/licenses/>.
*/

// Package httpgzip implements an http.Handler wrapper adding gzip
// compression for appropriate requests.
//
// It attempts to properly parse the request's Accept-Encoding header
// according to RFC 2616 and does not do a simple string search for
// "gzip" (which will fail to do the correct thing for values such as
// "*" or "identity,gzip;q=0"). It will serve either gzip or identity
// content codings, or return 406 Not Acceptable status if it can do
// neither.
//
// It works correctly with handlers which honour Range request headers
// (such as http.FileServer) by removing the Range header for requests
// which prefer gzip encoding. This is necessary since Range requests
// apply to the gzipped content but the wrapped handler is not aware
// of the compression when it writes byte ranges. The Accept-Ranges
// header is also stripped from corresponding responses.
//
// For requests which prefer gzip encoding a Content-Type header is
// set using http.DetectContentType if it is not set by the wrapped
// handler.
package httpgzip // import "xi2.org/x/httpgzip"

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"mime"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

// These constants are copied from the gzip package, so that code that
// imports this package does not also have to import "compress/gzip".
const (
	NoCompression      = gzip.NoCompression
	BestSpeed          = gzip.BestSpeed
	BestCompression    = gzip.BestCompression
	DefaultCompression = gzip.DefaultCompression
)

// DefaultContentTypes is the default list of content types for which
// a Handler considers gzip compression. This list originates from the
// file compression.conf within the Apache configuration found at
// https://html5boilerplate.com/.
var DefaultContentTypes = []string{
	"application/atom+xml",
	"application/javascript",
	"application/json",
	"application/ld+json",
	"application/manifest+json",
	"application/rdf+xml",
	"application/rss+xml",
	"application/schema+json",
	"application/vnd.geo+json",
	"application/vnd.ms-fontobject",
	"application/x-font-ttf",
	"application/x-javascript",
	"application/x-web-app-manifest+json",
	"application/xhtml+xml",
	"application/xml",
	"font/eot",
	"font/opentype",
	"image/bmp",
	"image/svg+xml",
	"image/vnd.microsoft.icon",
	"image/x-icon",
	"text/cache-manifest",
	"text/css",
	"text/html",
	"text/javascript",
	"text/plain",
	"text/vcard",
	"text/vnd.rim.location.xloc",
	"text/vtt",
	"text/x-component",
	"text/x-cross-domain-policy",
	"text/xml",
}

var gzipWriterPools = map[int]*sync.Pool{}

func init() {
	levels := map[int]struct{}{
		DefaultCompression: struct{}{},
		NoCompression:      struct{}{},
	}
	for i := BestSpeed; i <= BestCompression; i++ {
		levels[i] = struct{}{}
	}
	for k := range levels {
		level := k // create new variable for closure
		gzipWriterPools[level] = &sync.Pool{
			New: func() interface{} {
				w, _ := gzip.NewWriterLevel(nil, level)
				return w
			},
		}
	}
}

var gzipBufPool = sync.Pool{
	New: func() interface{} { return new(bytes.Buffer) },
}

// A gzipResponseWriter is a modified http.ResponseWriter. It adds
// gzip compression to certain responses, and there are two cases
// where this is done. Case 1 is when encs only allows gzip encoding
// and forbids identity. Case 2 is when encs prefers gzip encoding,
// the response is at least 512 bytes and the response's content type
// is in ctMap.
//
// A gzipResponseWriter sets the Content-Encoding and Content-Type
// headers when appropriate. It is important to call the Close method
// when writing is finished in order to flush and close the
// gzipResponseWriter. The slice encs must contain only encodings from
// {encGzip,encIdentity} and contain at least one encoding.
//
// If a gzip.Writer is used in order to write a response it will use a
// compression level of level.
type gzipResponseWriter struct {
	http.ResponseWriter
	httpStatus int
	ctMap      map[string]struct{}
	encs       []encoding
	level      int
	gw         *gzip.Writer
	buf        *bytes.Buffer
}

func newGzipResponseWriter(w http.ResponseWriter, ctMap map[string]struct{}, encs []encoding, level int) *gzipResponseWriter {
	buf := gzipBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	return &gzipResponseWriter{
		ResponseWriter: w,
		httpStatus:     http.StatusOK,
		ctMap:          ctMap,
		encs:           encs,
		level:          level,
		buf:            buf}
}

// init gets called by Write once at least 512 bytes have been written
// to the temporary buffer buf, or by Close if it has not yet been
// called. Firstly it determines the content type, either from the
// Content-Type header, or by calling http.DetectContentType on
// buf. Then, if needed, a gzip.Writer is initialized. Lastly,
// appropriate headers are set and the ResponseWriter's WriteHeader
// method is called.
func (w *gzipResponseWriter) init() {
	cth := w.Header().Get("Content-Type")
	var ct string
	if cth != "" {
		ct = cth
	} else {
		ct = http.DetectContentType(w.buf.Bytes())
	}
	var gzipContentType bool
	if mt, _, err := mime.ParseMediaType(ct); err == nil {
		if _, ok := w.ctMap[mt]; ok {
			gzipContentType = true
		}
	}
	var useGzip bool
	if w.Header().Get("Content-Encoding") == "" && w.encs[0] == encGzip {
		if gzipContentType && w.buf.Len() >= 512 || len(w.encs) == 1 {
			useGzip = true
		}
	}
	if useGzip {
		w.gw = gzipWriterPools[w.level].Get().(*gzip.Writer)
		w.gw.Reset(w.ResponseWriter)
		w.Header().Del("Content-Length")
		w.Header().Set("Content-Encoding", "gzip")
	}
	w.Header().Del("Accept-Ranges")
	if cth == "" {
		w.Header().Set("Content-Type", ct)
	}
	w.ResponseWriter.WriteHeader(w.httpStatus)
}

func (w *gzipResponseWriter) Write(p []byte) (int, error) {
	var n, written int
	var err error
	if w.buf != nil {
		written = w.buf.Len()
		_, _ = w.buf.Write(p)
		if w.buf.Len() < 512 {
			return len(p), nil
		}
		w.init()
		p = w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
	}
	switch {
	case w.gw != nil:
		n, err = w.gw.Write(p)
	default:
		n, err = w.ResponseWriter.Write(p)
	}
	n -= written
	if n < 0 {
		n = 0
	}
	return n, err
}

func (w *gzipResponseWriter) WriteHeader(httpStatus int) {
	// postpone WriteHeader call until end of init method
	w.httpStatus = httpStatus
}

func (w *gzipResponseWriter) Close() (err error) {
	if w.buf != nil {
		w.init()
		p := w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
		switch {
		case w.gw != nil:
			_, err = w.gw.Write(p)
		default:
			_, err = w.ResponseWriter.Write(p)
		}
	}
	if w.gw != nil {
		e := w.gw.Close()
		if e != nil {
			err = e
		}
		gzipWriterPools[w.level].Put(w.gw)
		w.gw = nil
	}
	return err
}

// An encoding is a supported content coding.
type encoding int

const (
	encIdentity encoding = iota
	encGzip
)

// acceptedEncodings returns the supported content codings that are
// accepted by the request r. It returns a slice of encodings in
// client preference order.
//
// If the Sec-WebSocket-Key header is present then compressed content
// encodings are not considered.
//
// ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
func acceptedEncodings(r *http.Request) []encoding {
	h := r.Header.Get("Accept-Encoding")
	swk := r.Header.Get("Sec-WebSocket-Key")
	if h == "" {
		return []encoding{encIdentity}
	}
	gzip := float64(-1)    // -1 means not accepted, 0 -> 1 means value of q
	identity := float64(0) // -1 means not accepted, 0 -> 1 means value of q
	for _, s := range strings.Split(h, ",") {
		f := strings.Split(s, ";")
		f0 := strings.ToLower(strings.Trim(f[0], " "))
		q := float64(1.0)
		if len(f) > 1 {
			f1 := strings.ToLower(strings.Trim(f[1], " "))
			if strings.HasPrefix(f1, "q=") {
				if flt, err := strconv.ParseFloat(f1[2:], 32); err == nil {
					if flt >= 0 && flt <= 1 {
						q = flt
					}
				}
			}
		}
		if (f0 == "gzip" || f0 == "*") && q > gzip && swk == "" {
			gzip = q
		}
		if (f0 == "gzip" || f0 == "*") && q == 0 {
			gzip = -1
		}
		if (f0 == "identity" || f0 == "*") && q > identity {
			identity = q
		}
		if (f0 == "identity" || f0 == "*") && q == 0 {
			identity = -1
		}
	}
	switch {
	case gzip == -1 && identity == -1:
		return []encoding{}
	case gzip == -1:
		return []encoding{encIdentity}
	case identity == -1:
		return []encoding{encGzip}
	case identity > gzip:
		return []encoding{encIdentity, encGzip}
	default:
		return []encoding{encGzip, encIdentity}
	}
}

// NewHandler returns a new http.Handler which wraps a handler h
// adding gzip compression to certain responses. There are two cases
// where gzip compression is done. Case 1 is responses whose requests
// only allow gzip encoding and forbid identity encoding (identity
// encoding meaning no encoding). Case 2 is responses whose requests
// prefer gzip encoding, whose size is at least 512 bytes and whose
// content types are in contentTypes. If contentTypes is nil then
// DefaultContentTypes is considered instead.
//
// The new http.Handler sets the Content-Encoding, Vary and
// Content-Type headers in its responses as appropriate. If a request
// expresses a preference for gzip encoding then any Range headers are
// removed from the request before it is passed through to h and
// Accept-Ranges headers are stripped from corresponding
// responses. This happens regardless of whether gzip encoding is
// eventually used in the response or not.
func NewHandler(h http.Handler, contentTypes []string) http.Handler {
	gzh, _ := NewHandlerLevel(h, contentTypes, DefaultCompression)
	return gzh
}

// NewHandlerLevel is like NewHandler but allows one to specify the
// gzip compression level instead of assuming DefaultCompression.
//
// The compression level can be DefaultCompression, NoCompression, or
// any integer value between BestSpeed and BestCompression
// inclusive. The error returned will be nil if the level is valid.
func NewHandlerLevel(h http.Handler, contentTypes []string, level int) (http.Handler, error) {
	switch {
	case level == DefaultCompression || level == NoCompression:
		// no action needed
	case level < BestSpeed || level > BestCompression:
		return nil, fmt.Errorf(
			"httpgzip: invalid compression level: %d", level)
	}
	if contentTypes == nil {
		contentTypes = DefaultContentTypes
	}
	ctMap := map[string]struct{}{}
	for _, ct := range contentTypes {
		ctMap[ct] = struct{}{}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// add Vary header
		w.Header().Add("Vary", "Accept-Encoding")
		// check client's accepted encodings
		encs := acceptedEncodings(r)
		// return if no acceptable encodings
		if len(encs) == 0 {
			w.WriteHeader(http.StatusNotAcceptable)
			return
		}
		if encs[0] == encGzip {
			// cannot accept Range requests for possibly gzipped
			// responses
			r.Header.Del("Range")
			// create new ResponseWriter
			w = newGzipResponseWriter(w, ctMap, encs, level)
			defer w.(*gzipResponseWriter).Close()
		}
		// call original handler's ServeHTTP
		h.ServeHTTP(w, r)
	}), nil
}
<commit_msg>Minor comment clarification<commit_after>/*
   Copyright 2015 The Httpgzip Authors. See the AUTHORS file at the
   top-level directory of this distribution and at
   <https://xi2.org/x/httpgzip/m/AUTHORS>.

   This file is part of Httpgzip.

   Httpgzip is free software: you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Httpgzip is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Httpgzip.  If not, see <https://www.gnu.org/licenses/>.
*/

// Package httpgzip implements an http.Handler wrapper adding gzip
// compression for appropriate requests.
//
// It attempts to properly parse the request's Accept-Encoding header
// according to RFC 2616 and does not do a simple string search for
// "gzip" (which will fail to do the correct thing for values such as
// "*" or "identity,gzip;q=0"). It will serve either gzip or identity
// content codings (identity meaning no encoding), or return 406 Not
// Acceptable status if it can do neither.
//
// It works correctly with handlers which honour Range request headers
// (such as http.FileServer) by removing the Range header for requests
// which prefer gzip encoding. This is necessary since Range requests
// apply to the gzipped content but the wrapped handler is not aware
// of the compression when it writes byte ranges. The Accept-Ranges
// header is also stripped from corresponding responses.
//
// For requests which prefer gzip encoding a Content-Type header is
// set using http.DetectContentType if it is not set by the wrapped
// handler.
package httpgzip // import "xi2.org/x/httpgzip"

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"mime"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

// These constants are copied from the gzip package, so that code that
// imports this package does not also have to import "compress/gzip".
const (
	NoCompression      = gzip.NoCompression
	BestSpeed          = gzip.BestSpeed
	BestCompression    = gzip.BestCompression
	DefaultCompression = gzip.DefaultCompression
)

// DefaultContentTypes is the default list of content types for which
// a Handler considers gzip compression. This list originates from the
// file compression.conf within the Apache configuration found at
// https://html5boilerplate.com/.
var DefaultContentTypes = []string{
	"application/atom+xml",
	"application/javascript",
	"application/json",
	"application/ld+json",
	"application/manifest+json",
	"application/rdf+xml",
	"application/rss+xml",
	"application/schema+json",
	"application/vnd.geo+json",
	"application/vnd.ms-fontobject",
	"application/x-font-ttf",
	"application/x-javascript",
	"application/x-web-app-manifest+json",
	"application/xhtml+xml",
	"application/xml",
	"font/eot",
	"font/opentype",
	"image/bmp",
	"image/svg+xml",
	"image/vnd.microsoft.icon",
	"image/x-icon",
	"text/cache-manifest",
	"text/css",
	"text/html",
	"text/javascript",
	"text/plain",
	"text/vcard",
	"text/vnd.rim.location.xloc",
	"text/vtt",
	"text/x-component",
	"text/x-cross-domain-policy",
	"text/xml",
}

var gzipWriterPools = map[int]*sync.Pool{}

func init() {
	levels := map[int]struct{}{
		DefaultCompression: struct{}{},
		NoCompression:      struct{}{},
	}
	for i := BestSpeed; i <= BestCompression; i++ {
		levels[i] = struct{}{}
	}
	for k := range levels {
		level := k // create new variable for closure
		gzipWriterPools[level] = &sync.Pool{
			New: func() interface{} {
				w, _ := gzip.NewWriterLevel(nil, level)
				return w
			},
		}
	}
}

var gzipBufPool = sync.Pool{
	New: func() interface{} { return new(bytes.Buffer) },
}

// A gzipResponseWriter is a modified http.ResponseWriter. It adds
// gzip compression to certain responses, and there are two cases
// where this is done. Case 1 is when encs only allows gzip encoding
// and forbids identity. Case 2 is when encs prefers gzip encoding,
// the response is at least 512 bytes and the response's content type
// is in ctMap.
//
// A gzipResponseWriter sets the Content-Encoding and Content-Type
// headers when appropriate. It is important to call the Close method
// when writing is finished in order to flush and close the
// gzipResponseWriter. The slice encs must contain only encodings from
// {encGzip,encIdentity} and contain at least one encoding.
//
// If a gzip.Writer is used in order to write a response it will use a
// compression level of level.
type gzipResponseWriter struct {
	http.ResponseWriter
	httpStatus int
	ctMap      map[string]struct{}
	encs       []encoding
	level      int
	gw         *gzip.Writer
	buf        *bytes.Buffer
}

func newGzipResponseWriter(w http.ResponseWriter, ctMap map[string]struct{}, encs []encoding, level int) *gzipResponseWriter {
	buf := gzipBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	return &gzipResponseWriter{
		ResponseWriter: w,
		httpStatus:     http.StatusOK,
		ctMap:          ctMap,
		encs:           encs,
		level:          level,
		buf:            buf}
}

// init gets called by Write once at least 512 bytes have been written
// to the temporary buffer buf, or by Close if it has not yet been
// called. Firstly it determines the content type, either from the
// Content-Type header, or by calling http.DetectContentType on
// buf. Then, if needed, a gzip.Writer is initialized. Lastly,
// appropriate headers are set and the ResponseWriter's WriteHeader
// method is called.
func (w *gzipResponseWriter) init() {
	cth := w.Header().Get("Content-Type")
	var ct string
	if cth != "" {
		ct = cth
	} else {
		ct = http.DetectContentType(w.buf.Bytes())
	}
	var gzipContentType bool
	if mt, _, err := mime.ParseMediaType(ct); err == nil {
		if _, ok := w.ctMap[mt]; ok {
			gzipContentType = true
		}
	}
	var useGzip bool
	if w.Header().Get("Content-Encoding") == "" && w.encs[0] == encGzip {
		if gzipContentType && w.buf.Len() >= 512 || len(w.encs) == 1 {
			useGzip = true
		}
	}
	if useGzip {
		w.gw = gzipWriterPools[w.level].Get().(*gzip.Writer)
		w.gw.Reset(w.ResponseWriter)
		w.Header().Del("Content-Length")
		w.Header().Set("Content-Encoding", "gzip")
	}
	w.Header().Del("Accept-Ranges")
	if cth == "" {
		w.Header().Set("Content-Type", ct)
	}
	w.ResponseWriter.WriteHeader(w.httpStatus)
}

func (w *gzipResponseWriter) Write(p []byte) (int, error) {
	var n, written int
	var err error
	if w.buf != nil {
		written = w.buf.Len()
		_, _ = w.buf.Write(p)
		if w.buf.Len() < 512 {
			return len(p), nil
		}
		w.init()
		p = w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
	}
	switch {
	case w.gw != nil:
		n, err = w.gw.Write(p)
	default:
		n, err = w.ResponseWriter.Write(p)
	}
	n -= written
	if n < 0 {
		n = 0
	}
	return n, err
}

func (w *gzipResponseWriter) WriteHeader(httpStatus int) {
	// postpone WriteHeader call until end of init method
	w.httpStatus = httpStatus
}

func (w *gzipResponseWriter) Close() (err error) {
	if w.buf != nil {
		w.init()
		p := w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
		switch {
		case w.gw != nil:
			_, err = w.gw.Write(p)
		default:
			_, err = w.ResponseWriter.Write(p)
		}
	}
	if w.gw != nil {
		e := w.gw.Close()
		if e != nil {
			err = e
		}
		gzipWriterPools[w.level].Put(w.gw)
		w.gw = nil
	}
	return err
}

// An encoding is a supported content coding.
type encoding int

const (
	encIdentity encoding = iota
	encGzip
)

// acceptedEncodings returns the supported content codings that are
// accepted by the request r. It returns a slice of encodings in
// client preference order.
//
// If the Sec-WebSocket-Key header is present then compressed content
// encodings are not considered.
//
// ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
func acceptedEncodings(r *http.Request) []encoding {
	h := r.Header.Get("Accept-Encoding")
	swk := r.Header.Get("Sec-WebSocket-Key")
	if h == "" {
		return []encoding{encIdentity}
	}
	gzip := float64(-1)    // -1 means not accepted, 0 -> 1 means value of q
	identity := float64(0) // -1 means not accepted, 0 -> 1 means value of q
	for _, s := range strings.Split(h, ",") {
		f := strings.Split(s, ";")
		f0 := strings.ToLower(strings.Trim(f[0], " "))
		q := float64(1.0)
		if len(f) > 1 {
			f1 := strings.ToLower(strings.Trim(f[1], " "))
			if strings.HasPrefix(f1, "q=") {
				if flt, err := strconv.ParseFloat(f1[2:], 32); err == nil {
					if flt >= 0 && flt <= 1 {
						q = flt
					}
				}
			}
		}
		if (f0 == "gzip" || f0 == "*") && q > gzip && swk == "" {
			gzip = q
		}
		if (f0 == "gzip" || f0 == "*") && q == 0 {
			gzip = -1
		}
		if (f0 == "identity" || f0 == "*") && q > identity {
			identity = q
		}
		if (f0 == "identity" || f0 == "*") && q == 0 {
			identity = -1
		}
	}
	switch {
	case gzip == -1 && identity == -1:
		return []encoding{}
	case gzip == -1:
		return []encoding{encIdentity}
	case identity == -1:
		return []encoding{encGzip}
	case identity > gzip:
		return []encoding{encIdentity, encGzip}
	default:
		return []encoding{encGzip, encIdentity}
	}
}

// NewHandler returns a new http.Handler which wraps a handler h
// adding gzip compression to certain responses. There are two cases
// where gzip compression is done. Case 1 is responses whose requests
// only allow gzip encoding and forbid identity encoding (identity
// encoding meaning no encoding). Case 2 is responses whose requests
// prefer gzip encoding, whose size is at least 512 bytes and whose
// content types are in contentTypes. If contentTypes is nil then
// DefaultContentTypes is considered instead.
//
// The new http.Handler sets the Content-Encoding, Vary and
// Content-Type headers in its responses as appropriate. If a request
// expresses a preference for gzip encoding then any Range headers are
// removed from the request before it is passed through to h and
// Accept-Ranges headers are stripped from corresponding
// responses. This happens regardless of whether gzip encoding is
// eventually used in the response or not.
func NewHandler(h http.Handler, contentTypes []string) http.Handler {
	gzh, _ := NewHandlerLevel(h, contentTypes, DefaultCompression)
	return gzh
}

// NewHandlerLevel is like NewHandler but allows one to specify the
// gzip compression level instead of assuming DefaultCompression.
//
// The compression level can be DefaultCompression, NoCompression, or
// any integer value between BestSpeed and BestCompression
// inclusive. The error returned will be nil if the level is valid.
func NewHandlerLevel(h http.Handler, contentTypes []string, level int) (http.Handler, error) {
	switch {
	case level == DefaultCompression || level == NoCompression:
		// no action needed
	case level < BestSpeed || level > BestCompression:
		return nil, fmt.Errorf(
			"httpgzip: invalid compression level: %d", level)
	}
	if contentTypes == nil {
		contentTypes = DefaultContentTypes
	}
	ctMap := map[string]struct{}{}
	for _, ct := range contentTypes {
		ctMap[ct] = struct{}{}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// add Vary header
		w.Header().Add("Vary", "Accept-Encoding")
		// check client's accepted encodings
		encs := acceptedEncodings(r)
		// return if no acceptable encodings
		if len(encs) == 0 {
			w.WriteHeader(http.StatusNotAcceptable)
			return
		}
		if encs[0] == encGzip {
			// cannot accept Range requests for possibly gzipped
			// responses
			r.Header.Del("Range")
			// create new ResponseWriter
			w = newGzipResponseWriter(w, ctMap, encs, level)
			defer w.(*gzipResponseWriter).Close()
		}
		// call original handler's ServeHTTP
		h.ServeHTTP(w, r)
	}), nil
}
<|endoftext|>


<commit_before>/*
	This file is part of LogHub.

	LogHub is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	LogHub is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with LogHub.  If not, see <http://www.gnu.org/licenses/>.
*/
package main

import (
	"net"
	"strconv"
	"testing"
)

func TestHubSetStatRead(t *testing.T) {
	hub := NewDefaultHub()
	defer hub.Close()

	logsCount := 10
	closeFuncs := make([]func(), 0, logsCount)

	for i := 0; i < logsCount; i++ {
		port := 10000 + i
		serverAddress := ":" + strconv.Itoa(port)
		messageHandler := newTestMessageHandler()

		if c, err := startServer(serverAddress, messageHandler); err != nil {
			t.Errorf("Failed to start LogHub server", err.Error())
			t.FailNow()
		} else {
			closeFuncs = append(closeFuncs, c)
		}

		hub.SetLogStat(net.IPv4(127, 0, 0, 1), &LogStat{0, 0, port})
	}

	entries := make(chan *LogEntry)
	var queries = []*LogQuery{&LogQuery{}}

	hub.ReadLog(queries, entries)

	cnt := 0
	for _ = range entries {
		cnt++
	}

	if cnt < testLogEntriesCount*logsCount {
		t.Errorf("Failed to get entries from the hub. Expected %d, got %d.", testLogEntriesCount*logsCount, cnt)
		t.FailNow()
	}
}
<commit_msg>Hub test server closing.<commit_after>/*
	This file is part of LogHub.

	LogHub is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	LogHub is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with LogHub.  If not, see <http://www.gnu.org/licenses/>.
*/
package main

import (
	"net"
	"strconv"
	"testing"
)

func TestHubSetStatRead(t *testing.T) {
	hub := NewDefaultHub()
	defer hub.Close()

	logsCount := 10
	closeFuncs := make([]func(), 0, logsCount)

	for i := 0; i < logsCount; i++ {
		port := 10000 + i
		serverAddress := ":" + strconv.Itoa(port)
		messageHandler := newTestMessageHandler()

		if c, err := startServer(serverAddress, messageHandler); err != nil {
			t.Errorf("Failed to start LogHub server", err.Error())
			t.FailNow()
		} else {
			closeFuncs = append(closeFuncs, c)
		}

		hub.SetLogStat(net.IPv4(127, 0, 0, 1), &LogStat{0, 0, port})
	}

	defer func() {
		for _, c := range closeFuncs {
			c()
		}
	}()

	entries := make(chan *LogEntry)
	var queries = []*LogQuery{&LogQuery{}}

	hub.ReadLog(queries, entries)

	cnt := 0
	for _ = range entries {
		cnt++
	}

	if cnt < testLogEntriesCount*logsCount {
		t.Errorf("Failed to get entries from the hub. Expected %d, got %d.", testLogEntriesCount*logsCount, cnt)
		t.FailNow()
	}
}
<|endoftext|>


<commit_before>package its

import (
	"os/exec"
	"regexp"
	"strings"
	"testing"

	"io/ioutil"

	"os"

	"fmt"

	"path/filepath"
	"runtime"
)

var (
	regexpCommandLine = regexp.MustCompile("('[^']*')|(\\S+)")
)

type IntegrationTest interface {
	RequireDriver(driverName string)

	SkipDriver(driverName string)

	SkipDrivers(driverNames ...string)

	ForceDriver(driverName string)

	Run(description string, action func())

	Cmd(commandLine string) IntegrationTest

	Machine(commandLine string) IntegrationTest

	DriverName() string

	Should() Assertions

	TearDown()
}

type Assertions interface {
	Succeed(messages ...string) Assertions

	Fail(errorMessages ...string) Assertions

	ContainLines(count int) Assertions

	ContainLine(index int, text string) Assertions

	EqualLine(index int, text string) Assertions
}

func NewTest(t *testing.T) IntegrationTest {
	storagePath, _ := ioutil.TempDir("", "docker")

	return &dockerMachineTest{
		t:           t,
		storagePath: storagePath,
	}
}

type dockerMachineTest struct {
	t                   *testing.T
	storagePath         string
	dockerMachineBinary string
	description         string
	skip                bool
	rawOutput           string
	lines               []string
	err                 error
	fatal               bool
	failed              bool
}

func (dmt *dockerMachineTest) RequireDriver(driverName string) {
	dmt.skipIf(dmt.DriverName() != driverName)
}

func (dmt *dockerMachineTest) SkipDriver(driverName string) {
	dmt.skipIf(dmt.DriverName() == driverName)
}

func (dmt *dockerMachineTest) SkipDrivers(driverNames ...string) {
	for _, driverName := range driverNames {
		dmt.skipIf(dmt.DriverName() == driverName)
	}
}

func (dmt *dockerMachineTest) ForceDriver(driverName string) {
	os.Setenv("DRIVER", driverName)
}

func (dmt *dockerMachineTest) skipIf(condition bool) {
	if condition {
		dmt.skip = true
	}
}

func (dmt *dockerMachineTest) Run(description string, action func()) {
	dmt.description = description
	dmt.rawOutput = ""
	dmt.lines = nil
	dmt.err = nil
	dmt.failed = false

	if dmt.skip {
		fmt.Printf("%s %s\n", yellow("[SKIP]"), description)
	} else {
		fmt.Printf("%s %s", yellow("[..]"), description)
		action()

		if dmt.fatal || dmt.failed {
			fmt.Printf("\r%s %s\n", red("[KO]"), description)
		} else {
			fmt.Printf("\r%s %s\n", green("[OK]"), description)
		}
	}
}

func red(message string) string {
	if runtime.GOOS == "windows" {
		return message
	}
	return "\033[1;31m" + message + "\033[0m"
}

func green(message string) string {
	if runtime.GOOS == "windows" {
		return message
	}
	return "\033[1;32m" + message + "\033[0m"
}

func yellow(message string) string {
	if runtime.GOOS == "windows" {
		return message
	}
	return "\033[1;33m" + message + "\033[0m"
}

func (dmt *dockerMachineTest) DriverName() string {
	driver := os.Getenv("DRIVER")
	if driver != "" {
		return driver
	}

	return "none"
}

func (dmt *dockerMachineTest) Should() Assertions {
	return dmt
}

func (dmt *dockerMachineTest) testedBinary() string {
	if dmt.dockerMachineBinary != "" {
		return dmt.dockerMachineBinary
	}

	var binary string
	if runtime.GOOS == "windows" {
		binary = "docker-machine.exe"
	} else {
		binary = "docker-machine"
	}

	_, file, _, _ := runtime.Caller(0)
	dir := filepath.Dir(file)

	for dir != "/" {
		path := filepath.Join(dir, "bin", binary)

		_, err := os.Stat(path)
		if err == nil {
			dmt.dockerMachineBinary = path
			return path
		}

		dir = filepath.Dir(dir)
	}

	if !dmt.fatal {
		dmt.fatal = true
		dmt.t.Errorf("Binary not found: %s", binary)
	}

	return ""
}

func (dmt *dockerMachineTest) Cmd(commandLine string) IntegrationTest {
	if dmt.fatal {
		return dmt
	}

	commandLine = dmt.replaceDriver(commandLine)
	commandLine = dmt.replaceMachinePath(commandLine)

	return dmt.cmd("bash", "-c", commandLine)
}

func (dmt *dockerMachineTest) Machine(commandLine string) IntegrationTest {
	if dmt.fatal {
		return dmt
	}

	commandLine = dmt.replaceDriver(commandLine)

	return dmt.cmd(dmt.testedBinary(), parseFields(commandLine)...)
}

func (dmt *dockerMachineTest) cmd(command string, args ...string) IntegrationTest {
	cmd := exec.Command(command, args...)
	cmd.Env = append(os.Environ(), "MACHINE_STORAGE_PATH="+dmt.storagePath)

	combinedOutput, err := cmd.CombinedOutput()

	dmt.rawOutput = string(combinedOutput)
	dmt.lines = strings.Split(strings.TrimSpace(dmt.rawOutput), "\n")
	dmt.err = err

	return dmt
}

func (dmt *dockerMachineTest) replaceMachinePath(commandLine string) string {
	return strings.Replace(commandLine, "machine", dmt.testedBinary(), -1)
}

func (dmt *dockerMachineTest) replaceDriver(commandLine string) string {
	return strings.Replace(commandLine, "$DRIVER", dmt.DriverName(), -1)
}

func parseFields(commandLine string) []string {
	fields := regexpCommandLine.FindAllString(commandLine, -1)

	for i := range fields {
		if len(fields[i]) > 2 && strings.HasPrefix(fields[i], "'") && strings.HasSuffix(fields[i], "'") {
			fields[i] = fields[i][1 : len(fields[i])-1]
		}
	}

	return fields
}

func (dmt *dockerMachineTest) TearDown() {
	machines := filepath.Join(dmt.storagePath, "machines")

	dirs, _ := ioutil.ReadDir(machines)
	for _, dir := range dirs {
		dmt.Cmd("machine rm -f " + dir.Name())
	}

	os.RemoveAll(dmt.storagePath)
}

func (dmt *dockerMachineTest) ContainLines(count int) Assertions {
	if dmt.fatal {
		return dmt
	}

	if count != len(dmt.lines) {
		return dmt.failExpected("%d lines but got %d", count, len(dmt.lines))
	}

	return dmt
}

func (dmt *dockerMachineTest) ContainLine(index int, text string) Assertions {
	if dmt.fatal {
		return dmt
	}

	if index >= len(dmt.lines) {
		return dmt.failExpected("at least %d lines\nGot %d", index+1, len(dmt.lines))
	}

	if !strings.Contains(dmt.lines[index], text) {
		return dmt.failExpected("line %d to contain '%s'\nGot '%s'", index, text, dmt.lines[index])
	}

	return dmt
}

func (dmt *dockerMachineTest) EqualLine(index int, text string) Assertions {
	if dmt.fatal {
		return dmt
	}

	if index >= len(dmt.lines) {
		return dmt.failExpected("at least %d lines\nGot %d", index+1, len(dmt.lines))
	}

	if text != dmt.lines[index] {
		return dmt.failExpected("line %d to be '%s'\nGot '%s'", index, text, dmt.lines[index])
	}

	return dmt
}

func (dmt *dockerMachineTest) Succeed(messages ...string) Assertions {
	if dmt.fatal {
		return dmt
	}

	if dmt.err != nil {
		return dmt.failExpected("to succeed\nFailed with %s\n%s", dmt.err, dmt.rawOutput)
	}

	for _, message := range messages {
		if !strings.Contains(dmt.rawOutput, message) {
			return dmt.failExpected("output to contain '%s'\nGot '%s'", message, dmt.rawOutput)
		}
	}

	return dmt
}

func (dmt *dockerMachineTest) Fail(errorMessages ...string) Assertions {
	if dmt.fatal {
		return dmt
	}

	if dmt.err == nil {
		return dmt.failExpected("to fail\nGot success\n%s", dmt.rawOutput)
	}

	for _, message := range errorMessages {
		if !strings.Contains(dmt.rawOutput, message) {
			return dmt.failExpected("output to contain '%s'\nGot '%s'", message, dmt.rawOutput)
		}
	}

	return dmt
}

func (dmt *dockerMachineTest) failExpected(message string, args ...interface{}) Assertions {
	allArgs := append([]interface{}{dmt.description}, args...)

	dmt.failed = true
	dmt.t.Errorf("%s\nExpected "+message, allArgs...)

	return dmt
}
<commit_msg>Add debug in case of IT failure<commit_after>package its

import (
	"os/exec"
	"regexp"
	"strings"
	"testing"

	"io/ioutil"

	"os"

	"fmt"

	"path/filepath"
	"runtime"
)

var (
	regexpCommandLine = regexp.MustCompile("('[^']*')|(\\S+)")
)

type IntegrationTest interface {
	RequireDriver(driverName string)

	SkipDriver(driverName string)

	SkipDrivers(driverNames ...string)

	ForceDriver(driverName string)

	Run(description string, action func())

	Cmd(commandLine string) IntegrationTest

	Machine(commandLine string) IntegrationTest

	DriverName() string

	Should() Assertions

	TearDown()
}

type Assertions interface {
	Succeed(messages ...string) Assertions

	Fail(errorMessages ...string) Assertions

	ContainLines(count int) Assertions

	ContainLine(index int, text string) Assertions

	EqualLine(index int, text string) Assertions
}

func NewTest(t *testing.T) IntegrationTest {
	storagePath, _ := ioutil.TempDir("", "docker")

	return &dockerMachineTest{
		t:           t,
		storagePath: storagePath,
	}
}

type dockerMachineTest struct {
	t                   *testing.T
	storagePath         string
	dockerMachineBinary string
	description         string
	skip                bool
	rawOutput           string
	lines               []string
	err                 error
	fatal               bool
	failed              bool
}

func (dmt *dockerMachineTest) RequireDriver(driverName string) {
	dmt.skipIf(dmt.DriverName() != driverName)
}

func (dmt *dockerMachineTest) SkipDriver(driverName string) {
	dmt.skipIf(dmt.DriverName() == driverName)
}

func (dmt *dockerMachineTest) SkipDrivers(driverNames ...string) {
	for _, driverName := range driverNames {
		dmt.skipIf(dmt.DriverName() == driverName)
	}
}

func (dmt *dockerMachineTest) ForceDriver(driverName string) {
	os.Setenv("DRIVER", driverName)
}

func (dmt *dockerMachineTest) skipIf(condition bool) {
	if condition {
		dmt.skip = true
	}
}

func (dmt *dockerMachineTest) Run(description string, action func()) {
	dmt.description = description
	dmt.rawOutput = ""
	dmt.lines = nil
	dmt.err = nil
	dmt.failed = false

	if dmt.skip {
		fmt.Printf("%s %s\n", yellow("[SKIP]"), description)
	} else {
		fmt.Printf("%s %s", yellow("[..]"), description)
		action()

		if dmt.fatal || dmt.failed {
			fmt.Printf("\r%s %s\n", red("[KO]"), description)
		} else {
			fmt.Printf("\r%s %s\n", green("[OK]"), description)
		}
	}
}

func red(message string) string {
	if runtime.GOOS == "windows" {
		return message
	}
	return "\033[1;31m" + message + "\033[0m"
}

func green(message string) string {
	if runtime.GOOS == "windows" {
		return message
	}
	return "\033[1;32m" + message + "\033[0m"
}

func yellow(message string) string {
	if runtime.GOOS == "windows" {
		return message
	}
	return "\033[1;33m" + message + "\033[0m"
}

func (dmt *dockerMachineTest) DriverName() string {
	driver := os.Getenv("DRIVER")
	if driver != "" {
		return driver
	}

	return "none"
}

func (dmt *dockerMachineTest) Should() Assertions {
	return dmt
}

func (dmt *dockerMachineTest) testedBinary() string {
	if dmt.dockerMachineBinary != "" {
		return dmt.dockerMachineBinary
	}

	var binary string
	if runtime.GOOS == "windows" {
		binary = "docker-machine.exe"
	} else {
		binary = "docker-machine"
	}

	_, file, _, _ := runtime.Caller(0)
	dir := filepath.Dir(file)

	for dir != "/" {
		path := filepath.Join(dir, "bin", binary)

		_, err := os.Stat(path)
		if err == nil {
			dmt.dockerMachineBinary = path
			return path
		}

		dir = filepath.Dir(dir)
	}

	if !dmt.fatal {
		dmt.fatal = true
		dmt.t.Errorf("Binary not found: %s", binary)
	}

	return ""
}

func (dmt *dockerMachineTest) Cmd(commandLine string) IntegrationTest {
	if dmt.fatal {
		return dmt
	}

	commandLine = dmt.replaceDriver(commandLine)
	commandLine = dmt.replaceMachinePath(commandLine)

	return dmt.cmd("bash", "-c", commandLine)
}

func (dmt *dockerMachineTest) Machine(commandLine string) IntegrationTest {
	if dmt.fatal {
		return dmt
	}

	commandLine = dmt.replaceDriver(commandLine)

	return dmt.cmd(dmt.testedBinary(), parseFields(commandLine)...)
}

func (dmt *dockerMachineTest) cmd(command string, args ...string) IntegrationTest {
	cmd := exec.Command(command, args...)
	cmd.Env = append(os.Environ(), "MACHINE_STORAGE_PATH="+dmt.storagePath)

	combinedOutput, err := cmd.CombinedOutput()

	dmt.rawOutput = string(combinedOutput)
	dmt.lines = strings.Split(strings.TrimSpace(dmt.rawOutput), "\n")
	dmt.err = err

	return dmt
}

func (dmt *dockerMachineTest) replaceMachinePath(commandLine string) string {
	return strings.Replace(commandLine, "machine", dmt.testedBinary(), -1)
}

func (dmt *dockerMachineTest) replaceDriver(commandLine string) string {
	return strings.Replace(commandLine, "$DRIVER", dmt.DriverName(), -1)
}

func parseFields(commandLine string) []string {
	fields := regexpCommandLine.FindAllString(commandLine, -1)

	for i := range fields {
		if len(fields[i]) > 2 && strings.HasPrefix(fields[i], "'") && strings.HasSuffix(fields[i], "'") {
			fields[i] = fields[i][1 : len(fields[i])-1]
		}
	}

	return fields
}

func (dmt *dockerMachineTest) TearDown() {
	machines := filepath.Join(dmt.storagePath, "machines")

	dirs, _ := ioutil.ReadDir(machines)
	for _, dir := range dirs {
		dmt.Cmd("machine rm -f " + dir.Name())
	}

	os.RemoveAll(dmt.storagePath)
}

func (dmt *dockerMachineTest) ContainLines(count int) Assertions {
	if dmt.fatal {
		return dmt
	}

	if count != len(dmt.lines) {
		return dmt.failExpected("%d lines but got %d\n%s", count, len(dmt.lines), dmt.rawOutput)
	}

	return dmt
}

func (dmt *dockerMachineTest) ContainLine(index int, text string) Assertions {
	if dmt.fatal {
		return dmt
	}

	if index >= len(dmt.lines) {
		return dmt.failExpected("at least %d lines\nGot %d", index+1, len(dmt.lines))
	}

	if !strings.Contains(dmt.lines[index], text) {
		return dmt.failExpected("line %d to contain '%s'\nGot '%s'", index, text, dmt.lines[index])
	}

	return dmt
}

func (dmt *dockerMachineTest) EqualLine(index int, text string) Assertions {
	if dmt.fatal {
		return dmt
	}

	if index >= len(dmt.lines) {
		return dmt.failExpected("at least %d lines\nGot %d", index+1, len(dmt.lines))
	}

	if text != dmt.lines[index] {
		return dmt.failExpected("line %d to be '%s'\nGot '%s'", index, text, dmt.lines[index])
	}

	return dmt
}

func (dmt *dockerMachineTest) Succeed(messages ...string) Assertions {
	if dmt.fatal {
		return dmt
	}

	if dmt.err != nil {
		return dmt.failExpected("to succeed\nFailed with %s\n%s", dmt.err, dmt.rawOutput)
	}

	for _, message := range messages {
		if !strings.Contains(dmt.rawOutput, message) {
			return dmt.failExpected("output to contain '%s'\nGot '%s'", message, dmt.rawOutput)
		}
	}

	return dmt
}

func (dmt *dockerMachineTest) Fail(errorMessages ...string) Assertions {
	if dmt.fatal {
		return dmt
	}

	if dmt.err == nil {
		return dmt.failExpected("to fail\nGot success\n%s", dmt.rawOutput)
	}

	for _, message := range errorMessages {
		if !strings.Contains(dmt.rawOutput, message) {
			return dmt.failExpected("output to contain '%s'\nGot '%s'", message, dmt.rawOutput)
		}
	}

	return dmt
}

func (dmt *dockerMachineTest) failExpected(message string, args ...interface{}) Assertions {
	allArgs := append([]interface{}{dmt.description}, args...)

	dmt.failed = true
	dmt.t.Errorf("%s\nExpected "+message, allArgs...)

	return dmt
}
<|endoftext|>