<commit_before>// Copyright (c) 2015 Mute Communications Ltd.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ctrlengine

import (
	"bytes"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"time"

	"github.com/codegangsta/cli"
	"github.com/mutecomm/mute/def"
	"github.com/mutecomm/mute/log"
	mixclient "github.com/mutecomm/mute/mix/client"
	"github.com/mutecomm/mute/msgdb"
	"github.com/mutecomm/mute/release"
	"github.com/mutecomm/mute/uid/identity"
	"github.com/mutecomm/mute/util"
	"github.com/mutecomm/mute/util/git"
	"github.com/mutecomm/mute/util/gotool"
	"github.com/mutecomm/mute/util/times"
)

type getPastExecution func(mappedID string) (int64, error)

func checkExecution(
	mappedID, period string,
	getPast getPastExecution,
) (bool, int64, error) {
	duration, err := time.ParseDuration(period)
	if err != nil {
		return false, 0, err
	}
	now := time.Now().UTC()
	if duration == 0 {
		// always execution for 0 duration
		return true, now.Unix(), nil
	}
	past, err := getPast(mappedID)
	if err != nil {
		return false, 0, err
	}
	if past != 0 {
		if time.Unix(past, 0).Add(duration).After(now) {
			return false, 0, err
		}
	}
	return true, now.Unix(), nil
}

func (ce *CtrlEngine) upkeepAll(
	c *cli.Context,
	unmappedID,
	period string,
	statfp io.Writer,
) error {
	mappedID, err := identity.Map(unmappedID)
	if err != nil {
		return err
	}

	exec, now, err := checkExecution(mappedID, period,
		func(mappedID string) (int64, error) {
			return ce.msgDB.GetUpkeepAll(mappedID)
		})
	if err != nil {
		return err
	}
	if !exec {
		log.Info(statfp, "ctrlengine: upkeep all not due")
		fmt.Fprintf(statfp, "ctrlengine: upkeep all not due\n")
		return nil
	}

	// `upkeep accounts`
	if err := ce.upkeepAccounts(unmappedID, period, "2160h", statfp); err != nil {
		return err
	}

	// TODO: call all upkeep tasks in mutecrypt

	// record time of execution
	if err := ce.msgDB.SetUpkeepAll(mappedID, now); err != nil {
		return err
	}
	return nil
}

func writeConfigFile(homedir, domain string, config []byte) error {
	configdir := path.Join(homedir, "config")
	if err := os.MkdirAll(configdir, 0700); err != nil {
		return log.Error(err)
	}
	tmpfile := path.Join(configdir, domain+".new")
	os.Remove(tmpfile) // ignore error
	if err := ioutil.WriteFile(tmpfile, config, 0700); err != nil {
		return log.Error(err)
	}
	return os.Rename(tmpfile, path.Join(configdir, domain))
}

func (ce *CtrlEngine) upkeepFetchconf(
	msgDB *msgdb.MsgDB,
	homedir string,
	show bool,
	outfp, statfp io.Writer,
) error {
	netDomain, pubkeyStr, configURL := def.ConfigParams()
	log.Infof("fetch config for '%s'", netDomain)
	fmt.Fprintf(statfp, "fetch config for '%s'\n", netDomain)
	publicKey, err := hex.DecodeString(pubkeyStr)
	if err != nil {
		log.Error(err)
	}
	ce.config.PublicKey = publicKey
	ce.config.URLList = "10," + configURL
	ce.config.Timeout = 0 // use default timeout
	if err := ce.config.Update(); err != nil {
		return log.Error(err)
	}
	jsn, err := json.Marshal(ce.config)
	if err != nil {
		return log.Error(err)
	}
	if err := msgDB.AddValue(netDomain, string(jsn)); err != nil {
		return err
	}
	// apply new configuration
	if err := def.InitMute(&ce.config); err != nil {
		return err
	}
	// format configuration nicely
	jsn, err = json.MarshalIndent(ce.config, "", "  ")
	if err != nil {
		return log.Error(err)
	}
	// write new configuration file
	if err := writeConfigFile(homedir, netDomain, jsn); err != nil {
		return err
	}
	// show new configuration
	if show {
		fmt.Fprintf(outfp, string(jsn)+"\n")
	}
	return nil
}

func updateMuteFromSource(outfp, statfp io.Writer, commit string) error {
	fmt.Fprintf(statfp, "updating Mute from source...\n")
	dir, err := exec.LookPath(os.Args[0])
	if err != nil {
		return err
	}
	// TODO: change path when release was done on github.com
	dir = path.Join(dir, "..", "..")

	// git status --porcelain
	fmt.Fprintf(statfp, "$ git status --porcelain\n")
	if err := git.Status(dir, statfp); err != nil {
		return log.Error(err)
	}

	// git checkout master
	fmt.Fprintf(statfp, "$ git checkout master\n")
	if err := git.Checkout(dir, "master", outfp, statfp); err != nil {
		return log.Error(err)
	}

	// git pull
	fmt.Fprintf(statfp, "$ git pull\n")
	if err := git.Pull(dir, outfp, statfp); err != nil {
		return err
	}

	// get current HEAD
	head, _, err := git.GetHead(dir, statfp)
	if err != nil {
		return log.Error(err)
	}

	// git checkout, if necessary
	var detached bool
	if head != commit {
		fmt.Fprintf(statfp, "$ git checkout\n")
		if err := git.Checkout(dir, commit, outfp, statfp); err != nil {
			return log.Error(err)
		}
		detached = true
	}

	// go generate -v mute/util/release
	fmt.Fprintf(statfp, "$ go generate -v mute/util/release\n")
	err = gotool.Generate(dir, "mute/util/release", outfp, statfp)
	if err != nil {
		return log.Error(err)
	}

	// go install -v mute/cmd/...
	fmt.Fprintf(statfp, "$ go install -v mute/cmd/...\n")
	if err := gotool.Install(dir, "mute/cmd/...", outfp, statfp); err != nil {
		return log.Error(err)
	}

	// go back to master, if necessary
	if detached {
		fmt.Fprintf(statfp, "$ git checkout master\n")
		if err := git.Checkout(dir, "master", outfp, statfp); err != nil {
			return log.Error(err)
		}
	}

	fmt.Fprintf(statfp, "Mute updated (restart it, if necessary)\n")
	return nil
}

func updateMuteBinaries(outfp, statfp io.Writer) error {
	fmt.Fprintf(statfp, "updating Mute binaries...\n")

	// "release.mutectrl.linux.amd64.hash": "SHA256 hash",
	// "release.mutectrl.linux.amd64.url": "https://mute.berlin/releases/...",

	// - find out which release to download (mutectrl, mutecrypt, and muteproto)
	// - download files
	// - compare hashes
	// - move binaries in place (os.Rename())

	// TODO: implement
	return nil
}

func (ce *CtrlEngine) upkeepUpdate(
	homedir string,
	/* source, binary bool, */
	outfp, statfp io.Writer,
) error {
	// make sure we have the most current config
	if err := ce.upkeepFetchconf(ce.msgDB, homedir, false, outfp, statfp); err != nil {
		return err
	}
	commit := ce.config.Map["release.Commit"]
	if release.Commit == commit {
		fmt.Fprintf(statfp, "Mute is up-to-date\n")
		return nil
	}
	// parse release date
	tRelease, err := time.Parse(git.Date, ce.config.Map["release.Date"])
	if err != nil {
		return err
	}
	// parse binary date
	tBinary, err := time.Parse(git.Date, release.Date)
	if err != nil {
		return err
	}
	// switch to UTC
	tRelease = tRelease.UTC()
	tBinary = tBinary.UTC()
	// compare dates
	if tBinary.After(tRelease) {
		fmt.Fprintf(statfp, "commits differ, but binary is newer than release date\n")
		fmt.Fprintf(statfp, "are you running a developer version?\n")
		return nil
	}
	/*
		// commits differ and release date is more current than binary -> update
		if !source && !binary {
			// try to determine update mode via mutegenerate
			// (should only exist for source releases)
			dir, err := exec.LookPath(os.Args[0])
			if err != nil {
				return err
			}
			dir = path.Join(dir, "..")
			cmd := exec.Command(path.Join(dir, "mutegenerate"), "-t")
			if err := cmd.Run(); err != nil {
				binary = true
			} else {
				source = true
			}
		}
		if source {
	*/
	if err := updateMuteFromSource(outfp, statfp, commit); err != nil {
		return err
	}
	/*
		} else {
			if err := updateMuteBinaries(outfp, statfp); err != nil {
				return err
			}
		}
	*/
	return nil
}

func (ce *CtrlEngine) upkeepAccounts(
	unmappedID, period, remaining string,
	statfp io.Writer,
) error {
	mappedID, err := identity.Map(unmappedID)
	if err != nil {
		return err
	}

	exec, now, err := checkExecution(mappedID, period,
		func(mappedID string) (int64, error) {
			return ce.msgDB.GetUpkeepAccounts(mappedID)
		})
	if err != nil {
		return err
	}
	if !exec {
		log.Info(statfp, "ctrlengine: upkeep accounts not due")
		fmt.Fprintf(statfp, "ctrlengine: upkeep accounts not due\n")
		return nil
	}

	remain, err := time.ParseDuration(remaining)
	if err != nil {
		return err
	}

	contacts, err := ce.msgDB.GetAccounts(mappedID)
	if err != nil {
		return err
	}

	for _, contact := range contacts {
		privkey, server, _, _, err := ce.msgDB.GetAccount(mappedID, contact)
		if err != nil {
			return err
		}
		last, err := ce.msgDB.GetAccountTime(mappedID, contact)
		if err != nil {
			return err
		}
		if last == 0 {
			last, err = mixclient.AccountStat(privkey, server, def.CACert)
			if err != nil {
				return err
			}
			err := ce.msgDB.SetAccountTime(mappedID, contact, last)
			if err != nil {
				return err
			}
		}
		if times.Now()+int64(remain.Seconds()) >= last {
			token, err := util.WalletGetToken(ce.client, def.AccdUsage, def.AccdOwner)
			if err != nil {
				return err
			}
			_, err = mixclient.PayAccount(privkey, token.Token, server, def.CACert)
			if err != nil {
				ce.client.UnlockToken(token.Hash)
				return log.Error(err)
			}
			ce.client.DelToken(token.Hash)
			last, err = mixclient.AccountStat(privkey, server, def.CACert)
			if err != nil {
				return err
			}
			err = ce.msgDB.SetAccountTime(mappedID, contact, last)
			if err != nil {
				return err
			}
		}
	}

	// record time of execution
	if err := ce.msgDB.SetUpkeepAccounts(mappedID, now); err != nil {
		return err
	}

	return nil
}

func mutecryptHashchainSync(c *cli.Context, domain string, passphrase []byte) error {
	args := []string{
		"--homedir", c.GlobalString("homedir"),
		"--loglevel", c.GlobalString("loglevel"),
		"--logdir", c.GlobalString("logdir"),
		"hashchain", "sync",
		"--domain", domain,
	}
	cmd := exec.Command("mutecrypt", args...)
	var errbuf bytes.Buffer
	cmd.Stderr = &errbuf
	ppR, ppW, err := os.Pipe()
	if err != nil {
		return err
	}
	defer ppR.Close()
	ppW.Write(passphrase)
	ppW.Close()
	cmd.ExtraFiles = append(cmd.ExtraFiles, ppR)
	if err := cmd.Run(); err != nil {
		return log.Error(err)
	}
	return nil
}

func mutecryptHashchainVerify(c *cli.Context, domain string, passphrase []byte) error {
	args := []string{
		"--homedir", c.GlobalString("homedir"),
		"--loglevel", c.GlobalString("loglevel"),
		"--logdir", c.GlobalString("logdir"),
		"hashchain", "sync",
		"--domain", domain,
	}
	cmd := exec.Command("mutecrypt", args...)
	var errbuf bytes.Buffer
	cmd.Stderr = &errbuf
	ppR, ppW, err := os.Pipe()
	if err != nil {
		return err
	}
	defer ppR.Close()
	ppW.Write(passphrase)
	ppW.Close()
	cmd.ExtraFiles = append(cmd.ExtraFiles, ppR)
	if err := cmd.Run(); err != nil {
		return log.Error(err)
	}
	return nil
}

func (ce *CtrlEngine) upkeepHashchain(c *cli.Context, domain string) error {
	// sync hashchain
	if err := mutecryptHashchainSync(c, domain, ce.passphrase); err != nil {
		return err
	}
	// verify hashchain
	// TODO: we only have to verify the new part, not the whole hashchain!
	if err := mutecryptHashchainVerify(c, domain, ce.passphrase); err != nil {
		return err
	}
	return nil
}
<commit_msg>mutectrl: improve error message<commit_after>// Copyright (c) 2015 Mute Communications Ltd.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ctrlengine

import (
	"bytes"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"time"

	"github.com/codegangsta/cli"
	"github.com/mutecomm/mute/def"
	"github.com/mutecomm/mute/log"
	mixclient "github.com/mutecomm/mute/mix/client"
	"github.com/mutecomm/mute/msgdb"
	"github.com/mutecomm/mute/release"
	"github.com/mutecomm/mute/uid/identity"
	"github.com/mutecomm/mute/util"
	"github.com/mutecomm/mute/util/git"
	"github.com/mutecomm/mute/util/gotool"
	"github.com/mutecomm/mute/util/times"
)

type getPastExecution func(mappedID string) (int64, error)

func checkExecution(
	mappedID, period string,
	getPast getPastExecution,
) (bool, int64, error) {
	duration, err := time.ParseDuration(period)
	if err != nil {
		return false, 0, err
	}
	now := time.Now().UTC()
	if duration == 0 {
		// always execution for 0 duration
		return true, now.Unix(), nil
	}
	past, err := getPast(mappedID)
	if err != nil {
		return false, 0, err
	}
	if past != 0 {
		if time.Unix(past, 0).Add(duration).After(now) {
			return false, 0, err
		}
	}
	return true, now.Unix(), nil
}

func (ce *CtrlEngine) upkeepAll(
	c *cli.Context,
	unmappedID,
	period string,
	statfp io.Writer,
) error {
	mappedID, err := identity.Map(unmappedID)
	if err != nil {
		return err
	}

	exec, now, err := checkExecution(mappedID, period,
		func(mappedID string) (int64, error) {
			return ce.msgDB.GetUpkeepAll(mappedID)
		})
	if err != nil {
		return err
	}
	if !exec {
		log.Info(statfp, "ctrlengine: upkeep all not due")
		fmt.Fprintf(statfp, "ctrlengine: upkeep all not due\n")
		return nil
	}

	// `upkeep accounts`
	if err := ce.upkeepAccounts(unmappedID, period, "2160h", statfp); err != nil {
		return err
	}

	// TODO: call all upkeep tasks in mutecrypt

	// record time of execution
	if err := ce.msgDB.SetUpkeepAll(mappedID, now); err != nil {
		return err
	}
	return nil
}

func writeConfigFile(homedir, domain string, config []byte) error {
	configdir := path.Join(homedir, "config")
	if err := os.MkdirAll(configdir, 0700); err != nil {
		return log.Error(err)
	}
	tmpfile := path.Join(configdir, domain+".new")
	os.Remove(tmpfile) // ignore error
	if err := ioutil.WriteFile(tmpfile, config, 0700); err != nil {
		return log.Error(err)
	}
	return os.Rename(tmpfile, path.Join(configdir, domain))
}

func (ce *CtrlEngine) upkeepFetchconf(
	msgDB *msgdb.MsgDB,
	homedir string,
	show bool,
	outfp, statfp io.Writer,
) error {
	netDomain, pubkeyStr, configURL := def.ConfigParams()
	log.Infof("fetch config for '%s'", netDomain)
	fmt.Fprintf(statfp, "fetch config for '%s'\n", netDomain)
	publicKey, err := hex.DecodeString(pubkeyStr)
	if err != nil {
		log.Error(err)
	}
	ce.config.PublicKey = publicKey
	ce.config.URLList = "10," + configURL
	ce.config.Timeout = 0 // use default timeout
	if err := ce.config.Update(); err != nil {
		return log.Error(err)
	}
	jsn, err := json.Marshal(ce.config)
	if err != nil {
		return log.Error(err)
	}
	if err := msgDB.AddValue(netDomain, string(jsn)); err != nil {
		return err
	}
	// apply new configuration
	if err := def.InitMute(&ce.config); err != nil {
		return err
	}
	// format configuration nicely
	jsn, err = json.MarshalIndent(ce.config, "", "  ")
	if err != nil {
		return log.Error(err)
	}
	// write new configuration file
	if err := writeConfigFile(homedir, netDomain, jsn); err != nil {
		return err
	}
	// show new configuration
	if show {
		fmt.Fprintf(outfp, string(jsn)+"\n")
	}
	return nil
}

func updateMuteFromSource(outfp, statfp io.Writer, commit string) error {
	fmt.Fprintf(statfp, "updating Mute from source...\n")
	dir, err := exec.LookPath(os.Args[0])
	if err != nil {
		return err
	}
	// TODO: change path when release was done on github.com
	dir = path.Join(dir, "..", "..")

	// git status --porcelain
	fmt.Fprintf(statfp, "$ git status --porcelain\n")
	if err := git.Status(dir, statfp); err != nil {
		return log.Error(err)
	}

	// git checkout master
	fmt.Fprintf(statfp, "$ git checkout master\n")
	if err := git.Checkout(dir, "master", outfp, statfp); err != nil {
		return log.Error(err)
	}

	// git pull
	fmt.Fprintf(statfp, "$ git pull\n")
	if err := git.Pull(dir, outfp, statfp); err != nil {
		return err
	}

	// get current HEAD
	head, _, err := git.GetHead(dir, statfp)
	if err != nil {
		return log.Error(err)
	}

	// git checkout, if necessary
	var detached bool
	if head != commit {
		fmt.Fprintf(statfp, "$ git checkout\n")
		if err := git.Checkout(dir, commit, outfp, statfp); err != nil {
			return log.Error(err)
		}
		detached = true
	}

	// go generate -v mute/util/release
	fmt.Fprintf(statfp, "$ go generate -v mute/util/release\n")
	err = gotool.Generate(dir, "mute/util/release", outfp, statfp)
	if err != nil {
		return log.Error(err)
	}

	// go install -v mute/cmd/...
	fmt.Fprintf(statfp, "$ go install -v mute/cmd/...\n")
	if err := gotool.Install(dir, "mute/cmd/...", outfp, statfp); err != nil {
		return log.Error(err)
	}

	// go back to master, if necessary
	if detached {
		fmt.Fprintf(statfp, "$ git checkout master\n")
		if err := git.Checkout(dir, "master", outfp, statfp); err != nil {
			return log.Error(err)
		}
	}

	fmt.Fprintf(statfp, "Mute updated (restart it, if necessary)\n")
	return nil
}

func updateMuteBinaries(outfp, statfp io.Writer) error {
	fmt.Fprintf(statfp, "updating Mute binaries...\n")

	// "release.mutectrl.linux.amd64.hash": "SHA256 hash",
	// "release.mutectrl.linux.amd64.url": "https://mute.berlin/releases/...",

	// - find out which release to download (mutectrl, mutecrypt, and muteproto)
	// - download files
	// - compare hashes
	// - move binaries in place (os.Rename())

	// TODO: implement
	return nil
}

func (ce *CtrlEngine) upkeepUpdate(
	homedir string,
	/* source, binary bool, */
	outfp, statfp io.Writer,
) error {
	// make sure we have the most current config
	if err := ce.upkeepFetchconf(ce.msgDB, homedir, false, outfp, statfp); err != nil {
		return err
	}
	commit := ce.config.Map["release.Commit"]
	if release.Commit == commit {
		fmt.Fprintf(statfp, "Mute is up-to-date\n")
		return nil
	}
	// parse release date
	tRelease, err := time.Parse(git.Date, ce.config.Map["release.Date"])
	if err != nil {
		return err
	}
	// parse binary date
	tBinary, err := time.Parse(git.Date, release.Date)
	if err != nil {
		return err
	}
	// switch to UTC
	tRelease = tRelease.UTC()
	tBinary = tBinary.UTC()
	// compare dates
	if tBinary.After(tRelease) {
		fmt.Fprintf(statfp, "commits differ, but binary is newer than release date\n")
		fmt.Fprintf(statfp, "are you running a developer version?\n")
		return nil
	}
	/*
		// commits differ and release date is more current than binary -> update
		if !source && !binary {
			// try to determine update mode via mutegenerate
			// (should only exist for source releases)
			dir, err := exec.LookPath(os.Args[0])
			if err != nil {
				return err
			}
			dir = path.Join(dir, "..")
			cmd := exec.Command(path.Join(dir, "mutegenerate"), "-t")
			if err := cmd.Run(); err != nil {
				binary = true
			} else {
				source = true
			}
		}
		if source {
	*/
	if err := updateMuteFromSource(outfp, statfp, commit); err != nil {
		return err
	}
	/*
		} else {
			if err := updateMuteBinaries(outfp, statfp); err != nil {
				return err
			}
		}
	*/
	return nil
}

func (ce *CtrlEngine) upkeepAccounts(
	unmappedID, period, remaining string,
	statfp io.Writer,
) error {
	mappedID, err := identity.Map(unmappedID)
	if err != nil {
		return err
	}

	exec, now, err := checkExecution(mappedID, period,
		func(mappedID string) (int64, error) {
			return ce.msgDB.GetUpkeepAccounts(mappedID)
		})
	if err != nil {
		return err
	}
	if !exec {
		log.Info(statfp, "ctrlengine: upkeep accounts not due")
		fmt.Fprintf(statfp, "ctrlengine: upkeep accounts not due\n")
		return nil
	}

	remain, err := time.ParseDuration(remaining)
	if err != nil {
		return err
	}

	contacts, err := ce.msgDB.GetAccounts(mappedID)
	if err != nil {
		return err
	}

	for _, contact := range contacts {
		privkey, server, _, _, err := ce.msgDB.GetAccount(mappedID, contact)
		if err != nil {
			return err
		}
		last, err := ce.msgDB.GetAccountTime(mappedID, contact)
		if err != nil {
			return err
		}
		if last == 0 {
			last, err = mixclient.AccountStat(privkey, server, def.CACert)
			if err != nil {
				return err
			}
			err := ce.msgDB.SetAccountTime(mappedID, contact, last)
			if err != nil {
				return err
			}
		}
		if times.Now()+int64(remain.Seconds()) >= last {
			token, err := util.WalletGetToken(ce.client, def.AccdUsage, def.AccdOwner)
			if err != nil {
				return err
			}
			_, err = mixclient.PayAccount(privkey, token.Token, server, def.CACert)
			if err != nil {
				ce.client.UnlockToken(token.Hash)
				return log.Error(err)
			}
			ce.client.DelToken(token.Hash)
			last, err = mixclient.AccountStat(privkey, server, def.CACert)
			if err != nil {
				return err
			}
			err = ce.msgDB.SetAccountTime(mappedID, contact, last)
			if err != nil {
				return err
			}
		}
	}

	// record time of execution
	if err := ce.msgDB.SetUpkeepAccounts(mappedID, now); err != nil {
		return err
	}

	return nil
}

func mutecryptHashchainSync(c *cli.Context, domain string, passphrase []byte) error {
	args := []string{
		"--homedir", c.GlobalString("homedir"),
		"--loglevel", c.GlobalString("loglevel"),
		"--logdir", c.GlobalString("logdir"),
		"hashchain", "sync",
		"--domain", domain,
	}
	cmd := exec.Command("mutecrypt", args...)
	var errbuf bytes.Buffer
	cmd.Stderr = &errbuf
	ppR, ppW, err := os.Pipe()
	if err != nil {
		return err
	}
	defer ppR.Close()
	ppW.Write(passphrase)
	ppW.Close()
	cmd.ExtraFiles = append(cmd.ExtraFiles, ppR)
	if err := cmd.Run(); err != nil {
		return log.Errorf("%s: %s", err, errbuf.String())
	}
	return nil
}

func mutecryptHashchainVerify(c *cli.Context, domain string, passphrase []byte) error {
	args := []string{
		"--homedir", c.GlobalString("homedir"),
		"--loglevel", c.GlobalString("loglevel"),
		"--logdir", c.GlobalString("logdir"),
		"hashchain", "sync",
		"--domain", domain,
	}
	cmd := exec.Command("mutecrypt", args...)
	var errbuf bytes.Buffer
	cmd.Stderr = &errbuf
	ppR, ppW, err := os.Pipe()
	if err != nil {
		return err
	}
	defer ppR.Close()
	ppW.Write(passphrase)
	ppW.Close()
	cmd.ExtraFiles = append(cmd.ExtraFiles, ppR)
	if err := cmd.Run(); err != nil {
		return log.Errorf("%s: %s", err, errbuf.String())
	}
	return nil
}

func (ce *CtrlEngine) upkeepHashchain(c *cli.Context, domain string) error {
	// sync hashchain
	if err := mutecryptHashchainSync(c, domain, ce.passphrase); err != nil {
		return err
	}
	// verify hashchain
	// TODO: we only have to verify the new part, not the whole hashchain!
	if err := mutecryptHashchainVerify(c, domain, ce.passphrase); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package integration

import (
	"bufio"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/apprenda/kismatic/integration/retry"
	homedir "github.com/mitchellh/go-homedir"
	. "github.com/onsi/ginkgo"
)

func leaveIt() bool {
	return os.Getenv("LEAVE_ARTIFACTS") != ""
}
func bailBeforeAnsible() bool {
	return os.Getenv("BAIL_BEFORE_ANSIBLE") != ""
}

func GetSSHKeyFile() (string, error) {
	dir, err := homedir.Dir()
	if err != nil {
		return "", err
	}
	return filepath.Join(dir, ".ssh", "kismatic-integration-testing.pem"), nil
}

type installOptions struct {
	allowPackageInstallation    bool
	autoConfigureDockerRegistry bool
	dockerRegistryIP            string
	dockerRegistryPort          int
	dockerRegistryCAPath        string
}

func installKismaticMini(node NodeDeets, sshKey string) error {
	By("Building a template")
	template, err := template.New("planAWSOverlay").Parse(planAWSOverlay)
	FailIfError(err, "Couldn't parse template")

	By("Building a plan to set up an overlay network cluster on this hardware")
	sshUser := node.SSHUser
	plan := PlanAWS{
		Etcd:                     []NodeDeets{node},
		Master:                   []NodeDeets{node},
		Worker:                   []NodeDeets{node},
		Ingress:                  []NodeDeets{node},
		MasterNodeFQDN:           node.Hostname,
		MasterNodeShortName:      node.Hostname,
		SSHKeyFile:               sshKey,
		SSHUser:                  sshUser,
		AllowPackageInstallation: true,
	}

	By("Writing plan file out to disk")
	f, err := os.Create("kismatic-testing.yaml")
	FailIfError(err, "Error waiting for nodes")
	defer f.Close()
	w := bufio.NewWriter(f)
	err = template.Execute(w, &plan)
	FailIfError(err, "Error filling in plan template")
	w.Flush()

	By("Validing our plan")
	cmd := exec.Command("./kismatic", "install", "validate", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	FailIfError(err, "Error validating plan")

	By("Punch it Chewie!")
	cmd = exec.Command("./kismatic", "install", "apply", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func installKismatic(nodes provisionedNodes, installOpts installOptions, sshKey string) error {
	By("Building a template")
	template, err := template.New("planAWSOverlay").Parse(planAWSOverlay)
	FailIfError(err, "Couldn't parse template")

	By("Building a plan to set up an overlay network cluster on this hardware")
	sshUser := nodes.master[0].SSHUser

	masterDNS := nodes.master[0].Hostname
	if nodes.dnsRecord != nil && nodes.dnsRecord.Name != "" {
		masterDNS = nodes.dnsRecord.Name
	}
	plan := PlanAWS{
		AllowPackageInstallation: installOpts.allowPackageInstallation,
		Etcd:                nodes.etcd,
		Master:              nodes.master,
		Worker:              nodes.worker,
		Ingress:             nodes.ingress,
		MasterNodeFQDN:      masterDNS,
		MasterNodeShortName: masterDNS,
		SSHKeyFile:          sshKey,
		SSHUser:             sshUser,
		AutoConfiguredDockerRegistry: installOpts.autoConfigureDockerRegistry,
		DockerRegistryCAPath:         installOpts.dockerRegistryCAPath,
		DockerRegistryIP:             installOpts.dockerRegistryIP,
		DockerRegistryPort:           installOpts.dockerRegistryPort,
	}

	f, err := os.Create("kismatic-testing.yaml")
	FailIfError(err, "Error creating plan")
	defer f.Close()
	w := bufio.NewWriter(f)
	err = template.Execute(w, &plan)
	FailIfError(err, "Error filling in plan template")
	w.Flush()

	By("Punch it Chewie!")
	cmd := exec.Command("./kismatic", "install", "apply", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	return cmd.Run()

}

func verifyMasterNodeFailure(nodes provisionedNodes, provisioner infrastructureProvisioner, sshKey string) error {
	By("Removing a Kubernetes master node")
	if err := provisioner.TerminateNode(nodes.master[0]); err != nil {
		return fmt.Errorf("Could not remove node: %v", err)
	}

	By("Rerunning Kuberang")
	if err := runViaSSH([]string{"sudo kuberang"}, []NodeDeets{nodes.master[1]}, sshKey, 5*time.Minute); err != nil {
		return fmt.Errorf("Failed to run kuberang: %v", err)
	}

	return nil
}

func verifyIngressNodes(nodes provisionedNodes, sshKey string) error {
	By("Adding a service and an ingress resource")
	addIngressResource(nodes.master[0], sshKey)

	By("Verifying the service is accessible via the ingress point(s)")
	for _, ingNode := range nodes.ingress {
		if err := verifyIngressPoint(ingNode); err != nil {
			return err
		}
	}

	return nil
}

func verifyIngressNode(node NodeDeets, sshKey string) error {
	By("Adding a service and an ingress resource")
	addIngressResource(node, sshKey)

	By("Verifying the service is accessible via the ingress point(s)")
	return verifyIngressPoint(node)
}

func addIngressResource(node NodeDeets, sshKey string) {
	err := copyFileToRemote("test-resources/ingress.yaml", "/tmp/ingress.yaml", node, sshKey, 1*time.Minute)
	FailIfError(err, "Error copying ingress test file")

	FailIfError(newTestIngressCert(), "Error creating certificates for HTTPS")

	err = copyFileToRemote("test-resources/ingress.yaml", "/tmp/tls.crt", node, sshKey, 1*time.Minute)
	FailIfError(err, "Error copying certificate to %s", node.PublicIP)
	err = copyFileToRemote("test-resources/ingress.yaml", "/tmp/tls.key", node, sshKey, 1*time.Minute)
	FailIfError(err, "Error copying certificate key to %s", node.PublicIP)

	err = runViaSSH([]string{"sudo kubectl create secret tls kismaticintegration-tls --cert=/tmp/tls.crt --key=/tmp/tls.key"}, []NodeDeets{node}, sshKey, 1*time.Minute)
	FailIfError(err, "Error creating tls secret")

	err = runViaSSH([]string{"sudo kubectl apply -f /tmp/ingress.yaml"}, []NodeDeets{node}, sshKey, 1*time.Minute)
	FailIfError(err, "Error creating ingress resources")
}

func newTestIngressCert() error {
	err := exec.Command("openssl", "req", "-x509", "-nodes", "-days", "365", "-newkey", "rsa:2048", "-keyout", "/tmp/tls.key", "-out", "/tmp/tls.crt", "-subj", "/CN=kismaticintegration.com").Run()
	return err
}

func verifyIngressPoint(node NodeDeets) error {
	// HTTP ingress
	certPath := "/tmp/tls.crt"
	url := "http://" + node.PublicIP + "/echo"
	if err := retry.WithBackoff(func() error { return ingressRequest(url, certPath) }, 10); err != nil {
		return err
	}
	// HTTPS ingress
	url = "https://" + node.PublicIP + "/echo-tls"
	if err := retry.WithBackoff(func() error { return ingressRequest(url, certPath) }, 7); err != nil {
		return err
	}
	return nil
}

func ingressRequest(url, certPath string) error {
	tlsConfig := &tls.Config{RootCAs: x509.NewCertPool()}
	tr := &http.Transport{
		TLSClientConfig: tlsConfig,
	}
	// Load the self signed cert
	cert, err := ioutil.ReadFile(certPath)
	if err != nil {
		return fmt.Errorf("Could not read certificate file, %v", err)
	}
	ok := tlsConfig.RootCAs.AppendCertsFromPEM(cert)
	if !ok {
		return fmt.Errorf("Could not read certificate PEM data, %v", err)
	}
	client := http.Client{
		Timeout:   1000 * time.Millisecond,
		Transport: tr,
	}
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return fmt.Errorf("Could not create request for ingress via %s, %v", url, err)
	}
	// Set the host header since this is not a real domain, curl $IP/echo -H 'Host: kismaticintegration.com'
	req.Host = "kismaticintegration.com"
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("Could not reach ingress via %s, %v", url, err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Ingress status code is not 200, got %d vi %s", resp.StatusCode, url)
	}

	return nil
}

func installKismaticWithABadNode() {
	By("Building a template")
	template, err := template.New("planAWSOverlay").Parse(planAWSOverlay)
	FailIfError(err, "Couldn't parse template")

	By("Faking infrastructure")
	fakeNode := NodeDeets{
		id:       "FakeId",
		PublicIP: "10.0.0.0",
		Hostname: "FakeHostname",
	}

	By("Building a plan to set up an overlay network cluster on this hardware")
	sshKey, err := GetSSHKeyFile()
	FailIfError(err, "Error getting SSH Key file")
	plan := PlanAWS{
		Etcd:                []NodeDeets{fakeNode},
		Master:              []NodeDeets{fakeNode},
		Worker:              []NodeDeets{fakeNode},
		Ingress:             []NodeDeets{fakeNode},
		MasterNodeFQDN:      "yep.nope",
		MasterNodeShortName: "yep",
		SSHUser:             "Billy Rubin",
		SSHKeyFile:          sshKey,
	}
	By("Writing plan file out to disk")
	f, err := os.Create("kismatic-testing.yaml")
	FailIfError(err, "Error waiting for nodes")
	defer f.Close()
	w := bufio.NewWriter(f)
	err = template.Execute(w, &plan)
	FailIfError(err, "Error filling in plan template")
	w.Flush()
	f.Close()

	By("Validing our plan")
	cmd := exec.Command("./kismatic", "install", "validate", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err == nil {
		Fail("Validation succeeeded even though it shouldn't have")
	}

	By("Well, try it anyway")
	cmd = exec.Command("./kismatic", "install", "apply", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err == nil {
		Fail("Application succeeeded even though it shouldn't have")
	}
}

func completesInTime(dothis func(), howLong time.Duration) bool {
	c1 := make(chan string, 1)
	go func() {
		dothis()
		c1 <- "completed"
	}()

	select {
	case <-c1:
		return true
	case <-time.After(howLong):
		return false
	}
}

func FailIfError(err error, message ...string) {
	if err != nil {
		log.Printf(message[0]+": %v\n%v", err, message[1:])
		Fail(message[0])
	}
}

func FailIfSuccess(err error, message ...string) {
	if err == nil {
		Fail("Expected failure")
	}
}

func FileExists(path string) bool {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return false
	}
	return true
}
<commit_msg>Revert back to InsecureSkipVerify, could not get it to work without.<commit_after>package integration

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/apprenda/kismatic/integration/retry"
	homedir "github.com/mitchellh/go-homedir"
	. "github.com/onsi/ginkgo"
)

func leaveIt() bool {
	return os.Getenv("LEAVE_ARTIFACTS") != ""
}
func bailBeforeAnsible() bool {
	return os.Getenv("BAIL_BEFORE_ANSIBLE") != ""
}

func GetSSHKeyFile() (string, error) {
	dir, err := homedir.Dir()
	if err != nil {
		return "", err
	}
	return filepath.Join(dir, ".ssh", "kismatic-integration-testing.pem"), nil
}

type installOptions struct {
	allowPackageInstallation    bool
	autoConfigureDockerRegistry bool
	dockerRegistryIP            string
	dockerRegistryPort          int
	dockerRegistryCAPath        string
}

func installKismaticMini(node NodeDeets, sshKey string) error {
	By("Building a template")
	template, err := template.New("planAWSOverlay").Parse(planAWSOverlay)
	FailIfError(err, "Couldn't parse template")

	By("Building a plan to set up an overlay network cluster on this hardware")
	sshUser := node.SSHUser
	plan := PlanAWS{
		Etcd:                     []NodeDeets{node},
		Master:                   []NodeDeets{node},
		Worker:                   []NodeDeets{node},
		Ingress:                  []NodeDeets{node},
		MasterNodeFQDN:           node.Hostname,
		MasterNodeShortName:      node.Hostname,
		SSHKeyFile:               sshKey,
		SSHUser:                  sshUser,
		AllowPackageInstallation: true,
	}

	By("Writing plan file out to disk")
	f, err := os.Create("kismatic-testing.yaml")
	FailIfError(err, "Error waiting for nodes")
	defer f.Close()
	w := bufio.NewWriter(f)
	err = template.Execute(w, &plan)
	FailIfError(err, "Error filling in plan template")
	w.Flush()

	By("Validing our plan")
	cmd := exec.Command("./kismatic", "install", "validate", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	FailIfError(err, "Error validating plan")

	By("Punch it Chewie!")
	cmd = exec.Command("./kismatic", "install", "apply", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func installKismatic(nodes provisionedNodes, installOpts installOptions, sshKey string) error {
	By("Building a template")
	template, err := template.New("planAWSOverlay").Parse(planAWSOverlay)
	FailIfError(err, "Couldn't parse template")

	By("Building a plan to set up an overlay network cluster on this hardware")
	sshUser := nodes.master[0].SSHUser

	masterDNS := nodes.master[0].Hostname
	if nodes.dnsRecord != nil && nodes.dnsRecord.Name != "" {
		masterDNS = nodes.dnsRecord.Name
	}
	plan := PlanAWS{
		AllowPackageInstallation: installOpts.allowPackageInstallation,
		Etcd:                nodes.etcd,
		Master:              nodes.master,
		Worker:              nodes.worker,
		Ingress:             nodes.ingress,
		MasterNodeFQDN:      masterDNS,
		MasterNodeShortName: masterDNS,
		SSHKeyFile:          sshKey,
		SSHUser:             sshUser,
		AutoConfiguredDockerRegistry: installOpts.autoConfigureDockerRegistry,
		DockerRegistryCAPath:         installOpts.dockerRegistryCAPath,
		DockerRegistryIP:             installOpts.dockerRegistryIP,
		DockerRegistryPort:           installOpts.dockerRegistryPort,
	}

	f, err := os.Create("kismatic-testing.yaml")
	FailIfError(err, "Error creating plan")
	defer f.Close()
	w := bufio.NewWriter(f)
	err = template.Execute(w, &plan)
	FailIfError(err, "Error filling in plan template")
	w.Flush()

	By("Punch it Chewie!")
	cmd := exec.Command("./kismatic", "install", "apply", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	return cmd.Run()

}

func verifyMasterNodeFailure(nodes provisionedNodes, provisioner infrastructureProvisioner, sshKey string) error {
	By("Removing a Kubernetes master node")
	if err := provisioner.TerminateNode(nodes.master[0]); err != nil {
		return fmt.Errorf("Could not remove node: %v", err)
	}

	By("Rerunning Kuberang")
	if err := runViaSSH([]string{"sudo kuberang"}, []NodeDeets{nodes.master[1]}, sshKey, 5*time.Minute); err != nil {
		return fmt.Errorf("Failed to run kuberang: %v", err)
	}

	return nil
}

func verifyIngressNodes(nodes provisionedNodes, sshKey string) error {
	By("Adding a service and an ingress resource")
	addIngressResource(nodes.master[0], sshKey)

	By("Verifying the service is accessible via the ingress point(s)")
	for _, ingNode := range nodes.ingress {
		if err := verifyIngressPoint(ingNode); err != nil {
			return err
		}
	}

	return nil
}

func verifyIngressNode(node NodeDeets, sshKey string) error {
	By("Adding a service and an ingress resource")
	addIngressResource(node, sshKey)

	By("Verifying the service is accessible via the ingress point(s)")
	return verifyIngressPoint(node)
}

func addIngressResource(node NodeDeets, sshKey string) {
	err := copyFileToRemote("test-resources/ingress.yaml", "/tmp/ingress.yaml", node, sshKey, 1*time.Minute)
	FailIfError(err, "Error copying ingress test file")

	err = runViaSSH([]string{"sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /tmp/tls.key -out /tmp/tls.crt -subj \"/CN=kismaticintegration.com\""}, []NodeDeets{node}, sshKey, 1*time.Minute)
	FailIfError(err, "Error creating certificates for HTTPs")

	err = runViaSSH([]string{"sudo kubectl create secret tls kismaticintegration-tls --cert=/tmp/tls.crt --key=/tmp/tls.key"}, []NodeDeets{node}, sshKey, 1*time.Minute)
	FailIfError(err, "Error creating tls secret")

	err = runViaSSH([]string{"sudo kubectl apply -f /tmp/ingress.yaml"}, []NodeDeets{node}, sshKey, 1*time.Minute)
	FailIfError(err, "Error creating ingress resources")
}

func newTestIngressCert() error {
	err := exec.Command("openssl", "req", "-x509", "-nodes", "-days", "365", "-newkey", "rsa:2048", "-keyout", "tls.key", "-out", "tls.crt", "-subj", "/CN=kismaticintegration.com").Run()
	return err
}

func verifyIngressPoint(node NodeDeets) error {
	// HTTP ingress
	url := "http://" + node.PublicIP + "/echo"
	if err := retry.WithBackoff(func() error { return ingressRequest(url) }, 10); err != nil {
		return err
	}
	// HTTPS ingress
	url = "https://" + node.PublicIP + "/echo-tls"
	if err := retry.WithBackoff(func() error { return ingressRequest(url) }, 7); err != nil {
		return err
	}
	return nil
}

func ingressRequest(url string) error {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := http.Client{
		Timeout:   1000 * time.Millisecond,
		Transport: tr,
	}
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return fmt.Errorf("Could not create request for ingress via %s, %v", url, err)
	}
	// Set the host header since this is not a real domain, curl $IP/echo -H 'Host: kismaticintegration.com'
	req.Host = "kismaticintegration.com"
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("Could not reach ingress via %s, %v", url, err)
	}
	if resp.StatusCode != 200 {
		return fmt.Errorf("Ingress status code is not 200, got %d vi %s", resp.StatusCode, url)
	}

	return nil
}

func installKismaticWithABadNode() {
	By("Building a template")
	template, err := template.New("planAWSOverlay").Parse(planAWSOverlay)
	FailIfError(err, "Couldn't parse template")

	By("Faking infrastructure")
	fakeNode := NodeDeets{
		id:       "FakeId",
		PublicIP: "10.0.0.0",
		Hostname: "FakeHostname",
	}

	By("Building a plan to set up an overlay network cluster on this hardware")
	sshKey, err := GetSSHKeyFile()
	FailIfError(err, "Error getting SSH Key file")
	plan := PlanAWS{
		Etcd:                []NodeDeets{fakeNode},
		Master:              []NodeDeets{fakeNode},
		Worker:              []NodeDeets{fakeNode},
		Ingress:             []NodeDeets{fakeNode},
		MasterNodeFQDN:      "yep.nope",
		MasterNodeShortName: "yep",
		SSHUser:             "Billy Rubin",
		SSHKeyFile:          sshKey,
	}
	By("Writing plan file out to disk")
	f, err := os.Create("kismatic-testing.yaml")
	FailIfError(err, "Error waiting for nodes")
	defer f.Close()
	w := bufio.NewWriter(f)
	err = template.Execute(w, &plan)
	FailIfError(err, "Error filling in plan template")
	w.Flush()
	f.Close()

	By("Validing our plan")
	cmd := exec.Command("./kismatic", "install", "validate", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err == nil {
		Fail("Validation succeeeded even though it shouldn't have")
	}

	By("Well, try it anyway")
	cmd = exec.Command("./kismatic", "install", "apply", "-f", f.Name())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err == nil {
		Fail("Application succeeeded even though it shouldn't have")
	}
}

func completesInTime(dothis func(), howLong time.Duration) bool {
	c1 := make(chan string, 1)
	go func() {
		dothis()
		c1 <- "completed"
	}()

	select {
	case <-c1:
		return true
	case <-time.After(howLong):
		return false
	}
}

func FailIfError(err error, message ...string) {
	if err != nil {
		log.Printf(message[0]+": %v\n%v", err, message[1:])
		Fail(message[0])
	}
}

func FailIfSuccess(err error, message ...string) {
	if err == nil {
		Fail("Expected failure")
	}
}

func FileExists(path string) bool {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return false
	}
	return true
}
<|endoftext|>


<commit_before>// Copyright (C) 2019-2021, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package timer

import (
	"time"
)

// EstimateETA attempts to estimate the remaining time for a job to finish given
// the [startTime] and it's current progress.
func EstimateETA(startTime time.Time, progress, end uint64) time.Duration {
	timeSpent := time.Since(startTime)

	percentExecuted := float64(progress) / float64(end)
	estimatedTotalDuration := time.Duration(float64(timeSpent) / percentExecuted)
	return estimatedTotalDuration - timeSpent
}
<commit_msg>Round eta calculation to nearest second<commit_after>// Copyright (C) 2019-2021, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package timer

import (
	"time"
)

// EstimateETA attempts to estimate the remaining time for a job to finish given
// the [startTime] and it's current progress.
func EstimateETA(startTime time.Time, progress, end uint64) time.Duration {
	timeSpent := time.Since(startTime)

	percentExecuted := float64(progress) / float64(end)
	estimatedTotalDuration := time.Duration(float64(timeSpent) / percentExecuted)
	eta := estimatedTotalDuration - timeSpent
	return eta.Round(time.Second)
}
<|endoftext|>


<commit_before>package v3

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/cloudfoundry-incubator/cf-test-helpers/generator"
	"github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	. "github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/onsi/ginkgo"
	. "github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/onsi/gomega"
	. "github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/onsi/gomega/gbytes"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
	. "github.com/cloudfoundry/cf-acceptance-tests/helpers/v3_helpers"
)

type ProcessStats struct {
	Instance struct {
		State string `json:"state"`
	} `json:"0"`
}

var _ = Describe("process", func() {
	var (
		appName     string
		appGuid     string
		packageGuid string
		spaceGuid   string
		token       string
	)

	BeforeEach(func() {
		appName = generator.PrefixedRandomName("CATS-APP-")
		spaceGuid = GetSpaceGuidFromName(context.RegularUserContext().Space)
		appGuid = CreateApp(appName, spaceGuid, `{"foo":"bar"}`)
		packageGuid = CreatePackage(appGuid)
		token := GetAuthToken()
		uploadUrl := fmt.Sprintf("%s%s/v3/packages/%s/upload", config.Protocol(), config.ApiEndpoint, packageGuid)
		UploadPackage(uploadUrl, assets.NewAssets().DoraZip, token)
		WaitForPackageToBeReady(packageGuid)
	})

	AfterEach(func() {
		FetchRecentLogs(appGuid, token, config)
		DeleteApp(appGuid)
	})

	Describe("terminating an instance", func() {
		var (
			index       = 0
			processType = "web"
			webProcess  Process
		)

		BeforeEach(func() {
			dropletGuid := StageBuildpackPackage(packageGuid, "ruby_buildpack")
			WaitForDropletToStage(dropletGuid)

			AssignDropletToApp(appGuid, dropletGuid)

			processes := GetProcesses(appGuid, appName)
			webProcess = GetProcessByType(processes, "web")

			CreateAndMapRoute(appGuid, context.RegularUserContext().Space, helpers.LoadConfig().AppsDomain, webProcess.Name)

			StartApp(appGuid)

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", webProcess.Name)))
		})

		Context("/v3/apps/:guid/processes/:type/instances/:index", func() {
			It("restarts the instance", func() {
				statsUrl := fmt.Sprintf("/v2/apps/%s/stats", webProcess.Guid)
				statsBody := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
				statsJSON := ProcessStats{}
				json.Unmarshal(statsBody, &statsJSON)

				Expect(statsJSON.Instance.State).To(Equal("RUNNING"))

				terminateUrl := fmt.Sprintf("/v3/apps/%s/processes/%s/instances/%d", appGuid, processType, index)
				cf.Cf("curl", terminateUrl, "-X", "DELETE").Wait(DEFAULT_TIMEOUT)

				// Note that this depends on a 30s run loop waking up in Diego.
				Eventually(func() string {
					statsBodyAfter := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
					json.Unmarshal(statsBodyAfter, &statsJSON)
					return statsJSON.Instance.State
				}, 35*time.Second).Should(Equal("STARTING"))
			})
		})

		Context("/v3/processes/:guid/instances/:index", func() {
			It("restarts the instance", func() {
				statsUrl := fmt.Sprintf("/v2/apps/%s/stats", webProcess.Guid)
				statsBody := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
				statsJSON := ProcessStats{}
				json.Unmarshal(statsBody, &statsJSON)

				Expect(statsJSON.Instance.State).To(Equal("RUNNING"))

				terminateUrl := fmt.Sprintf("/v3/processes/%s/instances/%d", webProcess.Guid, index)
				cf.Cf("curl", terminateUrl, "-X", "DELETE").Wait(DEFAULT_TIMEOUT)

				// Note that this depends on a 30s run loop waking up in Diego.
				Eventually(func() string {
					statsBodyAfter := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
					json.Unmarshal(statsBodyAfter, &statsJSON)
					return statsJSON.Instance.State
				}, 35*time.Second).Should(Equal("STARTING"))
			})
		})
	})
})
<commit_msg>try to fix flakey process termination test<commit_after>package v3

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/cloudfoundry-incubator/cf-test-helpers/generator"
	"github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	. "github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/onsi/ginkgo"
	. "github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/onsi/gomega"
	. "github.com/cloudfoundry/cf-acceptance-tests/Godeps/_workspace/src/github.com/onsi/gomega/gbytes"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
	. "github.com/cloudfoundry/cf-acceptance-tests/helpers/v3_helpers"
)

type ProcessStats struct {
	Instance struct {
		State string `json:"state"`
	} `json:"0"`
}

var _ = Describe("process", func() {
	var (
		appName     string
		appGuid     string
		packageGuid string
		spaceGuid   string
		token       string
	)

	BeforeEach(func() {
		appName = generator.PrefixedRandomName("CATS-APP-")
		spaceGuid = GetSpaceGuidFromName(context.RegularUserContext().Space)
		appGuid = CreateApp(appName, spaceGuid, `{"foo":"bar"}`)
		packageGuid = CreatePackage(appGuid)
		token := GetAuthToken()
		uploadUrl := fmt.Sprintf("%s%s/v3/packages/%s/upload", config.Protocol(), config.ApiEndpoint, packageGuid)
		UploadPackage(uploadUrl, assets.NewAssets().DoraZip, token)
		WaitForPackageToBeReady(packageGuid)
	})

	AfterEach(func() {
		FetchRecentLogs(appGuid, token, config)
		DeleteApp(appGuid)
	})

	Describe("terminating an instance", func() {
		var (
			index       = 0
			processType = "web"
			webProcess  Process
		)

		BeforeEach(func() {
			dropletGuid := StageBuildpackPackage(packageGuid, "ruby_buildpack")
			WaitForDropletToStage(dropletGuid)

			AssignDropletToApp(appGuid, dropletGuid)

			processes := GetProcesses(appGuid, appName)
			webProcess = GetProcessByType(processes, "web")

			CreateAndMapRoute(appGuid, context.RegularUserContext().Space, helpers.LoadConfig().AppsDomain, webProcess.Name)

			StartApp(appGuid)

			Eventually(func() string {
				return helpers.CurlAppRoot(webProcess.Name)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("apps").Wait(DEFAULT_TIMEOUT)).To(Say(fmt.Sprintf("%s\\s+started", webProcess.Name)))
		})

		Context("/v3/apps/:guid/processes/:type/instances/:index", func() {
			It("restarts the instance", func() {
				statsUrl := fmt.Sprintf("/v2/apps/%s/stats", webProcess.Guid)

				By("ensuring the instance is running")
				statsBody := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
				statsJSON := ProcessStats{}
				json.Unmarshal(statsBody, &statsJSON)
				Expect(statsJSON.Instance.State).To(Equal("RUNNING"))

				By("terminating the instance")
				terminateUrl := fmt.Sprintf("/v3/apps/%s/processes/%s/instances/%d", appGuid, processType, index)
				cf.Cf("curl", terminateUrl, "-X", "DELETE").Wait(DEFAULT_TIMEOUT)

				By("ensuring the instance is no longer running")
				// Note that this depends on a 30s run loop waking up in Diego.
				Eventually(func() string {
					statsBodyAfter := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
					json.Unmarshal(statsBodyAfter, &statsJSON)
					return statsJSON.Instance.State
				}, 35*time.Second).ShouldNot(Equal("RUNNING"))

				By("ensuring the instance is running again")
				Eventually(func() string {
					statsBodyAfter := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
					json.Unmarshal(statsBodyAfter, &statsJSON)
					return statsJSON.Instance.State
				}, 35*time.Second).Should(Equal("RUNNING"))
			})
		})

		Context("/v3/processes/:guid/instances/:index", func() {
			It("restarts the instance", func() {
				statsUrl := fmt.Sprintf("/v2/apps/%s/stats", webProcess.Guid)

				By("ensuring the instance is running")
				statsBody := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
				statsJSON := ProcessStats{}
				json.Unmarshal(statsBody, &statsJSON)
				Expect(statsJSON.Instance.State).To(Equal("RUNNING"))

				By("terminating the instance")
				terminateUrl := fmt.Sprintf("/v3/processes/%s/instances/%d", webProcess.Guid, index)
				cf.Cf("curl", terminateUrl, "-X", "DELETE").Wait(DEFAULT_TIMEOUT)

				By("ensuring the instance is no longer running")
				// Note that this depends on a 30s run loop waking up in Diego.
				Eventually(func() string {
					statsBodyAfter := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
					json.Unmarshal(statsBodyAfter, &statsJSON)
					return statsJSON.Instance.State
				}, 35*time.Second).ShouldNot(Equal("RUNNING"))

				By("ensuring the instance is running again")
				Eventually(func() string {
					statsBodyAfter := cf.Cf("curl", statsUrl).Wait(DEFAULT_TIMEOUT).Out.Contents()
					json.Unmarshal(statsBodyAfter, &statsJSON)
					return statsJSON.Instance.State
				}, 35*time.Second).Should(Equal("RUNNING"))
			})
		})
	})
})
<|endoftext|>


<commit_before>package termite

import (
	"fmt"
	"github.com/hanwen/go-fuse/fuse"
	"github.com/hanwen/go-fuse/unionfs"
	"io/ioutil"
	"log"
	"os"
	"os/user"
	"path/filepath"
	"strings"
)

type WorkerFuseFs struct {
	rwDir  string
	tmpDir string
	mount  string
	*fuse.MountState
	fsConnector *fuse.FileSystemConnector
	switchFs    *SwitchFileSystem
	unionFs     *unionfs.UnionFs
	procFs      *ProcFs
	nodeFs      *fuse.PathNodeFs
	// If nil, we are running this task.
	task *WorkerTask
}

func (me *WorkerFuseFs) Stop() {
	err := me.MountState.Unmount()
	if err != nil {
		// TODO - Should be fatal?
		log.Println("Unmount fail:", err)
	} else {
		// If the unmount fails, the RemoveAll will stat all
		// of the FUSE file system.
		os.RemoveAll(me.tmpDir)
	}
}

func (me *WorkerFuseFs) SetDebug(debug bool) {
	me.MountState.Debug = debug
	me.fsConnector.Debug = debug
	me.nodeFs.Debug = debug
}

func (me *Mirror) returnFuse(wfs *WorkerFuseFs) {
	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()

	wfs.task = nil
	wfs.SetDebug(false)

	if me.shuttingDown {
		wfs.Stop()
	} else {
		me.unusedFileSystems = append(me.unusedFileSystems, wfs)
	}
	me.workingFileSystems[wfs] = "", false
	me.cond.Broadcast()
}

func newWorkerFuseFs(tmpDir string, rpcFs fuse.FileSystem, writableRoot string,
nobody *user.User) (*WorkerFuseFs, os.Error) {
	tmpDir, err := ioutil.TempDir(tmpDir, "termite-task")
	if err != nil {
		return nil, err
	}
	w := WorkerFuseFs{
		tmpDir: tmpDir,
	}

	type dirInit struct {
		dst *string
		val string
	}

	for _, v := range []dirInit{
		dirInit{&w.rwDir, "rw"},
		dirInit{&w.mount, "mnt"},
	} {
		*v.dst = filepath.Join(w.tmpDir, v.val)
		err = os.Mkdir(*v.dst, 0700)
		if err != nil {
			return nil, err
		}
	}

	tmpBacking := filepath.Join(w.tmpDir, "tmp-backingstore")
	if err := os.Mkdir(tmpBacking, 0700); err != nil {
		return nil, err
	}

	rwFs := fuse.NewLoopbackFileSystem(w.rwDir)

	ttl := 30.0
	opts := unionfs.UnionFsOptions{
		BranchCacheTTLSecs:   ttl,
		DeletionCacheTTLSecs: ttl,
		DeletionDirName:      _DELETIONS,
	}
	mOpts := fuse.FileSystemOptions{
		EntryTimeout:    ttl,
		AttrTimeout:     ttl,
		NegativeTimeout: ttl,

		// 32-bit programs have trouble with 64-bit inode
		// numbers.
		PortableInodes: true,
	}

	tmpFs := fuse.NewLoopbackFileSystem(tmpBacking)

	w.procFs = NewProcFs()
	w.procFs.StripPrefix = w.mount
	if nobody != nil {
		w.procFs.Uid = nobody.Uid
	}

	w.unionFs = unionfs.NewUnionFs([]fuse.FileSystem{rwFs, rpcFs}, opts)
	swFs := []fuse.SwitchedFileSystem{
		{"", rpcFs, false},
		// TODO - configurable.
		{writableRoot, w.unionFs, false},
		// TODO - figure out how to mount this normally.
		{"var/tmp", tmpFs, true},
	}
	type submount struct {
		mountpoint string
		fs         fuse.FileSystem
	}
	mounts := []submount{
		{"proc", w.procFs},
		{"sys", &fuse.ReadonlyFileSystem{fuse.NewLoopbackFileSystem("/sys")}},
		{"dev", NewDevnullFs()},
	}
	fuseOpts := fuse.MountOptions{
		// Compilers are not that highly parallel.  A lower
		// number also helps stacktrace be less overwhelming.
		MaxBackground: 4,
	}
	if os.Geteuid() != 0 {
		// Typically, we run our tests as non-root under /tmp.
		// If we use go-fuse to mount /tmp, it will hide
		// writableRoot, and all our tests will fail.
		swFs = append(swFs,
			fuse.SwitchedFileSystem{"/tmp", tmpFs, true},
		)
	} else {
		fuseOpts.AllowOther = true
		mounts = append(mounts,
			submount{"tmp", tmpFs},
		)
	}

	w.switchFs =  NewSwitchFileSystem(fuse.NewSwitchFileSystem(swFs), w.unionFs)
	w.nodeFs = fuse.NewPathNodeFs(w.switchFs)
	w.fsConnector = fuse.NewFileSystemConnector(w.nodeFs, &mOpts)
	w.MountState = fuse.NewMountState(w.fsConnector)

	err = w.MountState.Mount(w.mount, &fuseOpts)
	if err != nil {
		return nil, err
	}
	for _, s := range mounts {
		code := w.fsConnector.Mount(w.nodeFs.Root().Inode(), s.mountpoint, fuse.NewPathNodeFs(s.fs), nil)
		if !code.Ok() {
			return nil, os.NewError(fmt.Sprintf("submount error for %v: %v", s.mountpoint, code))
		}
	}

	go w.MountState.Loop()

	return &w, nil
}

func (me *WorkerFuseFs) update(attrs []*FileAttr, origin *WorkerFuseFs) {
	paths := []string{}
	for _, attr := range attrs {
		path := strings.TrimLeft(attr.Path, "/")
		paths = append(paths, path)

		if origin == me {
			continue
		}

		if attr.Status.Ok() {
			me.nodeFs.Notify(path)
		} else {
			// Even if GetAttr() returns ENOENT, FUSE will
			// happily try to Open() the file afterwards.
			// So, issue entry notify for deletions rather
			// than inode notify.
			dir, base := filepath.Split(path)
			dir = filepath.Clean(dir)
			me.nodeFs.EntryNotify(dir, base)
		}
	}
	me.unionFs.DropBranchCache(paths)
	me.unionFs.DropDeletionCache()
}
<commit_msg>Update for new go-fuse.<commit_after>package termite

import (
	"fmt"
	"github.com/hanwen/go-fuse/fuse"
	"github.com/hanwen/go-fuse/unionfs"
	"io/ioutil"
	"log"
	"os"
	"os/user"
	"path/filepath"
	"strings"
)

type WorkerFuseFs struct {
	rwDir  string
	tmpDir string
	mount  string
	*fuse.MountState
	fsConnector *fuse.FileSystemConnector
	switchFs    *SwitchFileSystem
	unionFs     *unionfs.UnionFs
	procFs      *ProcFs
	nodeFs      *fuse.PathNodeFs
	// If nil, we are running this task.
	task *WorkerTask
}

func (me *WorkerFuseFs) Stop() {
	err := me.MountState.Unmount()
	if err != nil {
		// TODO - Should be fatal?
		log.Println("Unmount fail:", err)
	} else {
		// If the unmount fails, the RemoveAll will stat all
		// of the FUSE file system.
		os.RemoveAll(me.tmpDir)
	}
}

func (me *WorkerFuseFs) SetDebug(debug bool) {
	me.MountState.Debug = debug
	me.fsConnector.Debug = debug
	me.nodeFs.Debug = debug
}

func (me *Mirror) returnFuse(wfs *WorkerFuseFs) {
	me.fuseFileSystemsMutex.Lock()
	defer me.fuseFileSystemsMutex.Unlock()

	wfs.task = nil
	wfs.SetDebug(false)

	if me.shuttingDown {
		wfs.Stop()
	} else {
		me.unusedFileSystems = append(me.unusedFileSystems, wfs)
	}
	me.workingFileSystems[wfs] = "", false
	me.cond.Broadcast()
}

func newWorkerFuseFs(tmpDir string, rpcFs fuse.FileSystem, writableRoot string,
nobody *user.User) (*WorkerFuseFs, os.Error) {
	tmpDir, err := ioutil.TempDir(tmpDir, "termite-task")
	if err != nil {
		return nil, err
	}
	w := WorkerFuseFs{
		tmpDir: tmpDir,
	}

	type dirInit struct {
		dst *string
		val string
	}

	for _, v := range []dirInit{
		dirInit{&w.rwDir, "rw"},
		dirInit{&w.mount, "mnt"},
	} {
		*v.dst = filepath.Join(w.tmpDir, v.val)
		err = os.Mkdir(*v.dst, 0700)
		if err != nil {
			return nil, err
		}
	}

	tmpBacking := filepath.Join(w.tmpDir, "tmp-backingstore")
	if err := os.Mkdir(tmpBacking, 0700); err != nil {
		return nil, err
	}

	rwFs := fuse.NewLoopbackFileSystem(w.rwDir)

	ttl := 30.0
	opts := unionfs.UnionFsOptions{
		BranchCacheTTLSecs:   ttl,
		DeletionCacheTTLSecs: ttl,
		DeletionDirName:      _DELETIONS,
	}
	mOpts := fuse.FileSystemOptions{
		EntryTimeout:    ttl,
		AttrTimeout:     ttl,
		NegativeTimeout: ttl,

		// 32-bit programs have trouble with 64-bit inode
		// numbers.
		PortableInodes: true,
	}

	tmpFs := fuse.NewLoopbackFileSystem(tmpBacking)

	w.procFs = NewProcFs()
	w.procFs.StripPrefix = w.mount
	if nobody != nil {
		w.procFs.Uid = nobody.Uid
	}

	w.unionFs = unionfs.NewUnionFs([]fuse.FileSystem{rwFs, rpcFs}, opts)
	swFs := []fuse.SwitchedFileSystem{
		{"", rpcFs, false},
		// TODO - configurable.
		{writableRoot, w.unionFs, false},
		// TODO - figure out how to mount this normally.
		{"var/tmp", tmpFs, true},
	}
	type submount struct {
		mountpoint string
		fs         fuse.FileSystem
	}
	mounts := []submount{
		{"proc", w.procFs},
		{"sys", &fuse.ReadonlyFileSystem{fuse.NewLoopbackFileSystem("/sys")}},
		{"dev", NewDevnullFs()},
	}
	fuseOpts := fuse.MountOptions{
		// Compilers are not that highly parallel.  A lower
		// number also helps stacktrace be less overwhelming.
		MaxBackground: 4,
	}
	if os.Geteuid() != 0 {
		// Typically, we run our tests as non-root under /tmp.
		// If we use go-fuse to mount /tmp, it will hide
		// writableRoot, and all our tests will fail.
		swFs = append(swFs,
			fuse.SwitchedFileSystem{"/tmp", tmpFs, true},
		)
	} else {
		fuseOpts.AllowOther = true
		mounts = append(mounts,
			submount{"tmp", tmpFs},
		)
	}

	w.switchFs =  NewSwitchFileSystem(fuse.NewSwitchFileSystem(swFs), w.unionFs)
	pathOpts := fuse.PathNodeFsOptions{
		ClientInodes: true,
	}
	w.nodeFs = fuse.NewPathNodeFs(w.switchFs, &pathOpts)
	w.fsConnector = fuse.NewFileSystemConnector(w.nodeFs, &mOpts)
	w.MountState = fuse.NewMountState(w.fsConnector)

	err = w.MountState.Mount(w.mount, &fuseOpts)
	if err != nil {
		return nil, err
	}
	for _, s := range mounts {
		code := w.fsConnector.Mount(w.nodeFs.Root().Inode(), s.mountpoint, fuse.NewPathNodeFs(s.fs, nil), nil)
		if !code.Ok() {
			return nil, os.NewError(fmt.Sprintf("submount error for %v: %v", s.mountpoint, code))
		}
	}

	go w.MountState.Loop()

	return &w, nil
}

func (me *WorkerFuseFs) update(attrs []*FileAttr, origin *WorkerFuseFs) {
	paths := []string{}
	if me == origin {
		// TODO - should reread inode numbers, in case they
		// are reused.
	}

	for _, attr := range attrs {
		path := strings.TrimLeft(attr.Path, "/")
		paths = append(paths, path)

		if origin == me {
			continue
		}

		if attr.Status.Ok() {
			me.nodeFs.Notify(path)
		} else {
			// Even if GetAttr() returns ENOENT, FUSE will
			// happily try to Open() the file afterwards.
			// So, issue entry notify for deletions rather
			// than inode notify.
			dir, base := filepath.Split(path)
			dir = filepath.Clean(dir)
			me.nodeFs.EntryNotify(dir, base)
		}
	}
	me.unionFs.DropBranchCache(paths)
	me.unionFs.DropDeletionCache()
}
<|endoftext|>


<commit_before>// errchk $G -e $F.go

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Incorrect short declarations and redeclarations.

package main

func f1() int { return 1 }
func f2() (float, int) { return 1, 2 }
func f3() (float, int, string) { return 1, 2, "3" }

func main() {
	{
		// simple redeclaration
		i := f1();
		i := f1();	// ERROR "redeclared"
	}
	{
		// change of type for f
		i, f, s := f3();
		f, g, t := f3();	// ERROR "redeclared"
	}
	{
		// change of type for i
		i, f, s := f3();
		j, i, t := f3();	// ERROR "redeclared"
	}
	{
		// no new variables
		i, f, s := f3();
		i, f := f2();	// ERROR "redeclared"
	}
	{
		// single redeclaration
		i, f, s := f3();
		i := f1();	// ERROR "redeclared"
	}
		// double redeclaration
	{
		i, f, s := f3();
		i, f := f2();	// ERROR "redeclared"
	}
	{
		// triple redeclaration
		i, f, s := f3();
		i, f, s := f3();	// ERROR "redeclared"
	}
}
<commit_msg>Recognize gcco error messages.<commit_after>// errchk $G -e $F.go

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Incorrect short declarations and redeclarations.

package main

func f1() int { return 1 }
func f2() (float, int) { return 1, 2 }
func f3() (float, int, string) { return 1, 2, "3" }

func main() {
	{
		// simple redeclaration
		i := f1();
		i := f1();	// ERROR "redeclared|redefinition"
	}
	{
		// change of type for f
		i, f, s := f3();	// GCCGO_ERROR "previous"
		f, g, t := f3();	// ERROR "redeclared|redefinition"
	}
	{
		// change of type for i
		i, f, s := f3();	// GCCGO_ERROR "previous"
		j, i, t := f3();	// ERROR "redeclared|redefinition"
	}
	{
		// no new variables
		i, f, s := f3();
		i, f := f2();	// ERROR "redeclared|redefinition"
	}
	{
		// single redeclaration
		i, f, s := f3();	// GCCGO_ERROR "previous"
		i := f1();		// ERROR "redeclared|redefinition"
	}
		// double redeclaration
	{
		i, f, s := f3();
		i, f := f2();	// ERROR "redeclared|redefinition"
	}
	{
		// triple redeclaration
		i, f, s := f3();
		i, f, s := f3();	// ERROR "redeclared|redefinition"
	}
}
<|endoftext|>


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

package e2e

import (
	"fmt"
	"regexp"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/kubernetes/test/e2e/framework"

	. "github.com/onsi/ginkgo"
)

var _ = framework.KubeDescribe("GPU job processing", func() {
	f := framework.NewDefaultFramework("gpu")
	var cs kubernetes.Interface

	BeforeEach(func() {
		cs = f.ClientSet
	})

	It("Should run a job on a gpu node [Slow] [Zalando] [GPU]", func() {
		ns := f.Namespace.Name
		nameprefix := "gpu-test-"
		labels := map[string]string{
			"application": "vector-add",
		}

		By("Creating a vector pod which runs on a GPU node")
		pod := createVectorPod(nameprefix, ns, labels)
		_, err := cs.CoreV1().Pods(ns).Create(pod)
		framework.ExpectNoError(err, fmt.Errorf("Could not create POD %s", pod.Name))
		framework.ExpectNoError(f.WaitForPodRunning(pod.Name))
		for {
			p, err := cs.CoreV1().Pods(ns).Get(pod.Name, metav1.GetOptions{})
			if err != nil {
				framework.ExpectNoError(err, fmt.Errorf("Could not get POD %s", pod.Name))
				return
			}
			if p.Status.ContainerStatuses[0].State.Terminated == nil {
				time.Sleep(10 * time.Second)
				continue
			}
			n := p.Status.ContainerStatuses[0].State.Terminated.ExitCode
			if n < 1 {
				logs, err := getPodLogs(cs, ns, pod.Name, "cuda-vector-add", false)
				framework.ExpectNoError(err, "Should be able to get logs for pod %v", pod.Name)
				regex := regexp.MustCompile("PASSED")
				if regex.MatchString(logs) {
					return
				}
				framework.ExpectNoError(err, "Expected vector job to succeed")
				return
			}
			framework.ExpectNoError(fmt.Errorf("Expected POD %s to terminate with exit code 0", pod.Name))
			return
		}
	})
})
<commit_msg>fix error handling<commit_after>/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e

import (
	"fmt"
	"regexp"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/kubernetes/test/e2e/framework"

	. "github.com/onsi/ginkgo"
)

var _ = framework.KubeDescribe("GPU job processing", func() {
	f := framework.NewDefaultFramework("gpu")
	var cs kubernetes.Interface

	BeforeEach(func() {
		cs = f.ClientSet
	})

	It("Should run a job on a gpu node [Slow] [Zalando] [GPU]", func() {
		ns := f.Namespace.Name
		nameprefix := "gpu-test-"
		labels := map[string]string{
			"application": "vector-add",
		}

		By("Creating a vector pod which runs on a GPU node")
		pod := createVectorPod(nameprefix, ns, labels)
		_, err := cs.CoreV1().Pods(ns).Create(pod)
		framework.ExpectNoError(err, "Could not create POD %s", pod.Name)
		framework.ExpectNoError(f.WaitForPodRunning(pod.Name))
		for {
			p, err := cs.CoreV1().Pods(ns).Get(pod.Name, metav1.GetOptions{})
			if err != nil {
				framework.ExpectNoError(err, "Could not get POD %s", pod.Name)
				return
			}
			if p.Status.ContainerStatuses[0].State.Terminated == nil {
				time.Sleep(10 * time.Second)
				continue
			}
			n := p.Status.ContainerStatuses[0].State.Terminated.ExitCode
			if n != 0 {
				framework.ExpectNoError(fmt.Errorf("Expected POD %s to terminate with exit code 0", pod.Name))
				return
			}
			logs, err := getPodLogs(cs, ns, pod.Name, "cuda-vector-add", false)
			framework.ExpectNoError(err, "Should be able to get logs for pod %v", pod.Name)
			regex := regexp.MustCompile("PASSED")
			if regex.MatchString(logs) {
				return
			}
			framework.ExpectNoError(err, "Expected vector job to succeed")
			return
		}
	})
})
<|endoftext|>


<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build darwin freebsd linux windows
// +build !js
// +build !android
// +build !ios

package ui

import (
	"errors"
	"runtime"
	"sync"
	"time"

	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

type userInterface struct {
	title           string
	window          *glfw.Window
	width           int
	height          int
	scale           float64
	deviceScale     float64
	glfwScale       float64
	fullscreen      bool
	fullscreenScale float64
	funcs           chan func()
	running         bool
	sizeChanged     bool
	origPosX        int
	origPosY        int
	m               sync.Mutex
}

var currentUI *userInterface

func init() {
	if err := initialize(); err != nil {
		panic(err)
	}
}

func initialize() error {
	runtime.LockOSThread()

	if err := glfw.Init(); err != nil {
		return err
	}
	glfw.WindowHint(glfw.Visible, glfw.False)
	glfw.WindowHint(glfw.Resizable, glfw.False)
	glfw.WindowHint(glfw.ContextVersionMajor, 2)
	glfw.WindowHint(glfw.ContextVersionMinor, 1)

	// As start, create an window with temporary size to create OpenGL context thread.
	window, err := glfw.CreateWindow(16, 16, "", nil, nil)
	if err != nil {
		return err
	}
	hideConsoleWindowOnWindows()
	u := &userInterface{
		window:      window,
		funcs:       make(chan func()),
		sizeChanged: true,
		origPosX:    -1,
		origPosY:    -1,
	}
	u.window.MakeContextCurrent()
	glfw.SwapInterval(1)
	currentUI = u
	return nil
}

func RunMainThreadLoop(ch <-chan error) error {
	// TODO: Check this is done on the main thread.
	currentUI.setRunning(true)
	defer func() {
		currentUI.setRunning(false)
	}()
	for {
		select {
		case f := <-currentUI.funcs:
			f()
		case err := <-ch:
			// ch returns a value not only when an error occur but also it is closed.
			return err
		}
	}
}

func (u *userInterface) isRunning() bool {
	u.m.Lock()
	defer u.m.Unlock()
	return u.running
}

func (u *userInterface) setRunning(running bool) {
	u.m.Lock()
	defer u.m.Unlock()
	u.running = running
}

func (u *userInterface) runOnMainThread(f func() error) error {
	if u.funcs == nil {
		// already closed
		return nil
	}
	ch := make(chan struct{})
	var err error
	u.funcs <- func() {
		err = f()
		close(ch)
	}
	<-ch
	return err
}

func SetScreenSize(width, height int) bool {
	u := currentUI
	if !u.isRunning() {
		panic("ui: Run is not called yet")
	}
	r := false
	_ = u.runOnMainThread(func() error {
		r = u.setScreenSize(width, height, u.scale, u.fullscreen)
		return nil
	})
	return r
}

func SetScreenScale(scale float64) bool {
	u := currentUI
	if !u.isRunning() {
		panic("ui: Run is not called yet")
	}
	r := false
	_ = u.runOnMainThread(func() error {
		r = u.setScreenSize(u.width, u.height, scale, u.fullscreen)
		return nil
	})
	return r
}

func SetFullscreen(fullscreen bool) bool {
	u := currentUI
	if !u.isRunning() {
		panic("ui: Run is not called yet")
	}
	r := false
	_ = u.runOnMainThread(func() error {
		r = u.setScreenSize(u.width, u.height, u.scale, fullscreen)
		return nil
	})
	return r
}

func ScreenScale() float64 {
	u := currentUI
	if !u.isRunning() {
		return 0
	}
	s := 0.0
	_ = u.runOnMainThread(func() error {
		s = u.scale
		return nil
	})
	return s
}

func IsFullscreen() bool {
	u := currentUI
	if !u.isRunning() {
		return false
	}
	f := false
	_ = u.runOnMainThread(func() error {
		f = u.fullscreen
		return nil
	})
	return f
}

func ScreenOffset() (float64, float64) {
	u := currentUI
	if !u.isRunning() {
		return 0, 0
	}
	if !IsFullscreen() {
		return 0, 0
	}
	ox := 0.0
	oy := 0.0
	m := glfw.GetPrimaryMonitor()
	v := m.GetVideoMode()
	_ = u.runOnMainThread(func() error {
		ox = (float64(v.Width)*u.deviceScale/u.glfwScale - float64(u.width)*u.actualScreenScale()) / 2
		oy = (float64(v.Height)*u.deviceScale/u.glfwScale - float64(u.height)*u.actualScreenScale()) / 2
		return nil
	})
	return ox, oy
}

func adjustCursorPosition(x, y int) (int, int) {
	u := currentUI
	if !u.isRunning() {
		return x, y
	}
	ox, oy := ScreenOffset()
	s := 0.0
	_ = currentUI.runOnMainThread(func() error {
		s = currentUI.actualScreenScale()
		return nil
	})
	return x - int(ox/s), y - int(oy/s)
}

func SetCursorVisibility(visible bool) {
	// This can be called before Run: change the state asyncly.
	go func() {
		_ = currentUI.runOnMainThread(func() error {
			c := glfw.CursorNormal
			if !visible {
				c = glfw.CursorHidden
			}
			currentUI.window.SetInputMode(glfw.CursorMode, c)
			return nil
		})
	}()
}

func Run(width, height int, scale float64, title string, g GraphicsContext) error {
	u := currentUI
	// GLContext must be created before setting the screen size, which requires
	// swapping buffers.
	opengl.Init(currentUI.runOnMainThread)
	if err := u.runOnMainThread(func() error {
		m := glfw.GetPrimaryMonitor()
		v := m.GetVideoMode()
		if !u.setScreenSize(width, height, scale, false) {
			return errors.New("ui: Fail to set the screen size")
		}
		u.title = title
		u.window.SetTitle(title)
		u.window.Show()

		w, h := u.glfwSize()
		x := (v.Width - w) / 2
		y := (v.Height - h) / 3
		x, y = adjustWindowPosition(x, y)
		u.window.SetPos(x, y)
		return nil
	}); err != nil {
		return err
	}
	return u.loop(g)
}

func (u *userInterface) glfwSize() (int, int) {
	if u.glfwScale == 0 {
		u.glfwScale = glfwScale()
	}
	return int(float64(u.width) * u.scale * u.glfwScale), int(float64(u.height) * u.scale * u.glfwScale)
}

func (u *userInterface) getScale() float64 {
	if !u.fullscreen {
		return u.scale
	}
	if u.fullscreenScale == 0 {
		if u.glfwScale == 0 {
			u.glfwScale = glfwScale()
		}
		m := glfw.GetPrimaryMonitor()
		v := m.GetVideoMode()
		sw := float64(v.Width) / u.glfwScale / float64(u.width)
		sh := float64(v.Height) / u.glfwScale / float64(u.height)
		s := sw
		if s > sh {
			s = sh
		}
		u.fullscreenScale = s
	}
	return u.fullscreenScale
}

func (u *userInterface) actualScreenScale() float64 {
	if u.deviceScale == 0 {
		u.deviceScale = deviceScale()
	}
	return u.getScale() * u.deviceScale
}

func (u *userInterface) pollEvents() {
	glfw.PollEvents()
	if u.glfwScale == 0 {
		u.glfwScale = glfwScale()
	}
	currentInput.update(u.window, u.getScale()*u.glfwScale)
}

func (u *userInterface) update(g GraphicsContext) error {
	shouldClose := false
	_ = u.runOnMainThread(func() error {
		shouldClose = u.window.ShouldClose()
		return nil
	})
	if shouldClose {
		return &RegularTermination{}
	}

	actualScale := 0.0
	sizeChanged := false
	_ = u.runOnMainThread(func() error {
		if !u.sizeChanged {
			return nil
		}
		u.sizeChanged = false
		actualScale = u.actualScreenScale()
		sizeChanged = true
		return nil
	})
	if sizeChanged {
		g.SetSize(u.width, u.height, actualScale)
	}

	_ = u.runOnMainThread(func() error {
		u.pollEvents()
		for u.window.GetAttrib(glfw.Focused) == 0 {
			// Wait for an arbitrary period to avoid busy loop.
			time.Sleep(time.Second / 60)
			u.pollEvents()
			if u.window.ShouldClose() {
				return nil
			}
		}
		return nil
	})
	if err := g.Update(); err != nil {
		return err
	}
	return nil
}

func (u *userInterface) loop(g GraphicsContext) error {
	defer func() {
		_ = u.runOnMainThread(func() error {
			glfw.Terminate()
			return nil
		})
	}()
	for {
		if err := u.update(g); err != nil {
			return err
		}
		// The bound framebuffer must be the default one (0) before swapping buffers.
		if err := opengl.GetContext().BindScreenFramebuffer(); err != nil {
			return err
		}
		_ = u.runOnMainThread(func() error {
			u.swapBuffers()
			return nil
		})
	}
}

func (u *userInterface) swapBuffers() {
	u.window.SwapBuffers()
}

func (u *userInterface) setScreenSize(width, height int, scale float64, fullscreen bool) bool {
	if u.width == width && u.height == height && u.scale == scale && u.fullscreen == fullscreen {
		return false
	}

	origScale := u.scale
	u.scale = scale

	// On Windows, giving a too small width doesn't call a callback (#165).
	// To prevent hanging up, return asap if the width is too small.
	// 252 is an arbitrary number and I guess this is small enough.
	// TODO: The same check should be in ui_js.go
	const minWindowWidth = 252
	if int(float64(width)*u.actualScreenScale()) < minWindowWidth {
		u.scale = origScale
		return false
	}
	if u.width != width || u.height != height {
		u.width = width
		u.height = height
		u.fullscreenScale = 0
	}

	// To make sure the current existing framebuffers are rendered,
	// swap buffers here before SetSize is called.
	u.swapBuffers()

	u.fullscreen = fullscreen

	window := u.window
	if u.fullscreen {
		if u.origPosX < 0 && u.origPosY < 0 {
			u.origPosX, u.origPosY = window.GetPos()
		}
		m := glfw.GetPrimaryMonitor()
		v := m.GetVideoMode()
		window.SetMonitor(m, 0, 0, v.Width, v.Height, v.RefreshRate)
	} else {
		if u.origPosX >= 0 && u.origPosY >= 0 {
			x := u.origPosX
			y := u.origPosY
			window.SetMonitor(nil, x, y, 16, 16, 0)
			u.origPosX = -1
			u.origPosY = -1
		}
		ch := make(chan struct{})
		window.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) {
			window.SetFramebufferSizeCallback(nil)
			close(ch)
		})
		w, h := u.glfwSize()
		window.SetSize(w, h)
	event:
		for {
			glfw.PollEvents()
			select {
			case <-ch:
				break event
			default:
			}
		}
		// Window title might lost on macOS after coming back from fullscreen.
		u.window.SetTitle(u.title)
	}
	// TODO: Rename this variable?
	u.sizeChanged = true
	return true
}
<commit_msg>ui: Bug fix: SwapInterval needs to be called after SetMonitor (#357)<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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.

// +build darwin freebsd linux windows
// +build !js
// +build !android
// +build !ios

package ui

import (
	"errors"
	"runtime"
	"sync"
	"time"

	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

type userInterface struct {
	title           string
	window          *glfw.Window
	width           int
	height          int
	scale           float64
	deviceScale     float64
	glfwScale       float64
	fullscreen      bool
	fullscreenScale float64
	funcs           chan func()
	running         bool
	sizeChanged     bool
	origPosX        int
	origPosY        int
	m               sync.Mutex
}

var currentUI *userInterface

func init() {
	if err := initialize(); err != nil {
		panic(err)
	}
}

func initialize() error {
	runtime.LockOSThread()

	if err := glfw.Init(); err != nil {
		return err
	}
	glfw.WindowHint(glfw.Visible, glfw.False)
	glfw.WindowHint(glfw.Resizable, glfw.False)
	glfw.WindowHint(glfw.ContextVersionMajor, 2)
	glfw.WindowHint(glfw.ContextVersionMinor, 1)

	// As start, create an window with temporary size to create OpenGL context thread.
	window, err := glfw.CreateWindow(16, 16, "", nil, nil)
	if err != nil {
		return err
	}
	hideConsoleWindowOnWindows()
	u := &userInterface{
		window:      window,
		funcs:       make(chan func()),
		sizeChanged: true,
		origPosX:    -1,
		origPosY:    -1,
	}
	u.window.MakeContextCurrent()
	currentUI = u
	return nil
}

func RunMainThreadLoop(ch <-chan error) error {
	// TODO: Check this is done on the main thread.
	currentUI.setRunning(true)
	defer func() {
		currentUI.setRunning(false)
	}()
	for {
		select {
		case f := <-currentUI.funcs:
			f()
		case err := <-ch:
			// ch returns a value not only when an error occur but also it is closed.
			return err
		}
	}
}

func (u *userInterface) isRunning() bool {
	u.m.Lock()
	defer u.m.Unlock()
	return u.running
}

func (u *userInterface) setRunning(running bool) {
	u.m.Lock()
	defer u.m.Unlock()
	u.running = running
}

func (u *userInterface) runOnMainThread(f func() error) error {
	if u.funcs == nil {
		// already closed
		return nil
	}
	ch := make(chan struct{})
	var err error
	u.funcs <- func() {
		err = f()
		close(ch)
	}
	<-ch
	return err
}

func SetScreenSize(width, height int) bool {
	u := currentUI
	if !u.isRunning() {
		panic("ui: Run is not called yet")
	}
	r := false
	_ = u.runOnMainThread(func() error {
		r = u.setScreenSize(width, height, u.scale, u.fullscreen)
		return nil
	})
	return r
}

func SetScreenScale(scale float64) bool {
	u := currentUI
	if !u.isRunning() {
		panic("ui: Run is not called yet")
	}
	r := false
	_ = u.runOnMainThread(func() error {
		r = u.setScreenSize(u.width, u.height, scale, u.fullscreen)
		return nil
	})
	return r
}

func SetFullscreen(fullscreen bool) bool {
	u := currentUI
	if !u.isRunning() {
		panic("ui: Run is not called yet")
	}
	r := false
	_ = u.runOnMainThread(func() error {
		r = u.setScreenSize(u.width, u.height, u.scale, fullscreen)
		return nil
	})
	return r
}

func ScreenScale() float64 {
	u := currentUI
	if !u.isRunning() {
		return 0
	}
	s := 0.0
	_ = u.runOnMainThread(func() error {
		s = u.scale
		return nil
	})
	return s
}

func IsFullscreen() bool {
	u := currentUI
	if !u.isRunning() {
		return false
	}
	f := false
	_ = u.runOnMainThread(func() error {
		f = u.fullscreen
		return nil
	})
	return f
}

func ScreenOffset() (float64, float64) {
	u := currentUI
	if !u.isRunning() {
		return 0, 0
	}
	if !IsFullscreen() {
		return 0, 0
	}
	ox := 0.0
	oy := 0.0
	m := glfw.GetPrimaryMonitor()
	v := m.GetVideoMode()
	_ = u.runOnMainThread(func() error {
		ox = (float64(v.Width)*u.deviceScale/u.glfwScale - float64(u.width)*u.actualScreenScale()) / 2
		oy = (float64(v.Height)*u.deviceScale/u.glfwScale - float64(u.height)*u.actualScreenScale()) / 2
		return nil
	})
	return ox, oy
}

func adjustCursorPosition(x, y int) (int, int) {
	u := currentUI
	if !u.isRunning() {
		return x, y
	}
	ox, oy := ScreenOffset()
	s := 0.0
	_ = currentUI.runOnMainThread(func() error {
		s = currentUI.actualScreenScale()
		return nil
	})
	return x - int(ox/s), y - int(oy/s)
}

func SetCursorVisibility(visible bool) {
	// This can be called before Run: change the state asyncly.
	go func() {
		_ = currentUI.runOnMainThread(func() error {
			c := glfw.CursorNormal
			if !visible {
				c = glfw.CursorHidden
			}
			currentUI.window.SetInputMode(glfw.CursorMode, c)
			return nil
		})
	}()
}

func Run(width, height int, scale float64, title string, g GraphicsContext) error {
	u := currentUI
	// GLContext must be created before setting the screen size, which requires
	// swapping buffers.
	opengl.Init(currentUI.runOnMainThread)
	if err := u.runOnMainThread(func() error {
		m := glfw.GetPrimaryMonitor()
		v := m.GetVideoMode()
		if !u.setScreenSize(width, height, scale, false) {
			return errors.New("ui: Fail to set the screen size")
		}
		u.title = title
		u.window.SetTitle(title)
		u.window.Show()

		w, h := u.glfwSize()
		x := (v.Width - w) / 2
		y := (v.Height - h) / 3
		x, y = adjustWindowPosition(x, y)
		u.window.SetPos(x, y)
		return nil
	}); err != nil {
		return err
	}
	return u.loop(g)
}

func (u *userInterface) glfwSize() (int, int) {
	if u.glfwScale == 0 {
		u.glfwScale = glfwScale()
	}
	return int(float64(u.width) * u.scale * u.glfwScale), int(float64(u.height) * u.scale * u.glfwScale)
}

func (u *userInterface) getScale() float64 {
	if !u.fullscreen {
		return u.scale
	}
	if u.fullscreenScale == 0 {
		if u.glfwScale == 0 {
			u.glfwScale = glfwScale()
		}
		m := glfw.GetPrimaryMonitor()
		v := m.GetVideoMode()
		sw := float64(v.Width) / u.glfwScale / float64(u.width)
		sh := float64(v.Height) / u.glfwScale / float64(u.height)
		s := sw
		if s > sh {
			s = sh
		}
		u.fullscreenScale = s
	}
	return u.fullscreenScale
}

func (u *userInterface) actualScreenScale() float64 {
	if u.deviceScale == 0 {
		u.deviceScale = deviceScale()
	}
	return u.getScale() * u.deviceScale
}

func (u *userInterface) pollEvents() {
	glfw.PollEvents()
	if u.glfwScale == 0 {
		u.glfwScale = glfwScale()
	}
	currentInput.update(u.window, u.getScale()*u.glfwScale)
}

func (u *userInterface) update(g GraphicsContext) error {
	shouldClose := false
	_ = u.runOnMainThread(func() error {
		shouldClose = u.window.ShouldClose()
		return nil
	})
	if shouldClose {
		return &RegularTermination{}
	}

	actualScale := 0.0
	sizeChanged := false
	_ = u.runOnMainThread(func() error {
		if !u.sizeChanged {
			return nil
		}
		u.sizeChanged = false
		actualScale = u.actualScreenScale()
		sizeChanged = true
		return nil
	})
	if sizeChanged {
		g.SetSize(u.width, u.height, actualScale)
	}

	_ = u.runOnMainThread(func() error {
		u.pollEvents()
		for u.window.GetAttrib(glfw.Focused) == 0 {
			// Wait for an arbitrary period to avoid busy loop.
			time.Sleep(time.Second / 60)
			u.pollEvents()
			if u.window.ShouldClose() {
				return nil
			}
		}
		return nil
	})
	if err := g.Update(); err != nil {
		return err
	}
	return nil
}

func (u *userInterface) loop(g GraphicsContext) error {
	defer func() {
		_ = u.runOnMainThread(func() error {
			glfw.Terminate()
			return nil
		})
	}()
	for {
		if err := u.update(g); err != nil {
			return err
		}
		// The bound framebuffer must be the default one (0) before swapping buffers.
		if err := opengl.GetContext().BindScreenFramebuffer(); err != nil {
			return err
		}
		_ = u.runOnMainThread(func() error {
			u.swapBuffers()
			return nil
		})
	}
}

func (u *userInterface) swapBuffers() {
	u.window.SwapBuffers()
}

func (u *userInterface) setScreenSize(width, height int, scale float64, fullscreen bool) bool {
	if u.width == width && u.height == height && u.scale == scale && u.fullscreen == fullscreen {
		return false
	}

	origScale := u.scale
	u.scale = scale

	// On Windows, giving a too small width doesn't call a callback (#165).
	// To prevent hanging up, return asap if the width is too small.
	// 252 is an arbitrary number and I guess this is small enough.
	// TODO: The same check should be in ui_js.go
	const minWindowWidth = 252
	if int(float64(width)*u.actualScreenScale()) < minWindowWidth {
		u.scale = origScale
		return false
	}
	if u.width != width || u.height != height {
		u.width = width
		u.height = height
		u.fullscreenScale = 0
	}

	// To make sure the current existing framebuffers are rendered,
	// swap buffers here before SetSize is called.
	u.swapBuffers()

	u.fullscreen = fullscreen

	window := u.window
	if u.fullscreen {
		if u.origPosX < 0 && u.origPosY < 0 {
			u.origPosX, u.origPosY = window.GetPos()
		}
		m := glfw.GetPrimaryMonitor()
		v := m.GetVideoMode()
		window.SetMonitor(m, 0, 0, v.Width, v.Height, v.RefreshRate)
	} else {
		if u.origPosX >= 0 && u.origPosY >= 0 {
			x := u.origPosX
			y := u.origPosY
			window.SetMonitor(nil, x, y, 16, 16, 0)
			u.origPosX = -1
			u.origPosY = -1
		}
		ch := make(chan struct{})
		window.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) {
			window.SetFramebufferSizeCallback(nil)
			close(ch)
		})
		w, h := u.glfwSize()
		window.SetSize(w, h)
	event:
		for {
			glfw.PollEvents()
			select {
			case <-ch:
				break event
			default:
			}
		}
		// Window title might lost on macOS after coming back from fullscreen.
		u.window.SetTitle(u.title)
	}
	// SwapInterval is affected by the current monitor of the window.
	// This needs to be called at least after SetMonitor.
	// Without SwapInterval after SetMonitor, vsynch doesn't work (#357).
	glfw.SwapInterval(1)
	// TODO: Rename this variable?
	u.sizeChanged = true
	return true
}
<|endoftext|>