<commit_before>// +build mage

package main

import (
	"fmt"
	"time"

	"github.com/magefile/mage/mg"
	"github.com/magefile/mage/sh"
)

const (
	BaseBinaryVersion = "v0.15"
	BinaryName        = "mysensorsbootloader2mqtt"
	BaseDockerAccount = "mannkind"
)

// Everything below here is the same between projects
var BinaryArchs = []string{"amd64", "arm32v6", "arm64v8"}
var BinaryVersion = BaseBinaryVersion + time.Now().Format(".06002.1504")
var DockerImage = fmt.Sprintf("%s/%s", BaseDockerAccount, BinaryName)

// Commands
var g0 = sh.RunCmd("go")
var git = sh.RunCmd("git")
var docker = sh.RunCmd("docker")

type Go mg.Namespace
type Git mg.Namespace
type Docker mg.Namespace

// Default target to run when none is specified
var Default = All

// go:wire, go:format, go:vet, go:build, and go:test in that order
func All() {
	mg.SerialDeps(Go.Wire)
	mg.SerialDeps(Go.Format)
	mg.SerialDeps(Go.Vet)
	mg.SerialDeps(Go.Build)
	mg.SerialDeps(Go.Test)
	mg.SerialDeps(Go.Tidy)
}

// Remove the binary and architecture specific Dockerfiles
func Clean() error {
	fmt.Println("Cleaning")
	if err := sh.Run("rm", "-f", BinaryName); err != nil {
		return err
	}

	for _, arch := range BinaryArchs {
		if err := sh.Run("rm", "-f", "Dockerfile."+arch); err != nil {
			return err
		}
	}

	return nil
}

// Compile the application with the proper ldflags
func (Go) Build() error {
	fmt.Println("Building")
	return g0("build", "-ldflags", "-X \"main.Version="+BinaryVersion+"\" -X \"main.Name="+BinaryName+"\"", "-o", BinaryName, ".")
}

// Ensure the code is formatted properly
func (Go) Format() error {
	fmt.Println("Formatting")
	return g0("fmt", ".")
}

// Ensure the code passes vetting
func (Go) Vet() error {
	fmt.Println("Vetting")
	return g0("vet", ".")
}

// Get the compile-time DI tool
func (Go) GetWire() error {
	fmt.Println("Getting Wire")
	return g0("get", "github.com/google/wire/cmd/wire")
}

// Generate the dependencies at compile-time
func (Go) Wire() error {
	mg.SerialDeps(Go.GetWire)

	fmt.Println("Wiring")
	return sh.Run("wire", "gen")
}

// Run the application tests
func (Go) Test() error {
	mg.SerialDeps(Go.Build)

	fmt.Println("Testing")
	return g0("test", "--coverprofile", "/tmp/app.cover", "-v", ".")
}

// Run the tidy command
func (Go) Tidy() error {
	mg.SerialDeps(Go.Build)

	fmt.Println("Tidying")
	return g0("mod", "tidy")
}

// Create a new tag for the git repository using the generated binary version
func (Git) Tag() error {
	fmt.Println("Tagging Git Repo")
	return git("tag", "-f", BinaryVersion)
}

// Create a realese in Github by pushing the tags in the git repository
func (Git) Push() error {
	mg.SerialDeps(Git.Tag)

	fmt.Println("Pushing Git Repo Tags")
	if err := sh.Run("sed", "-i", "-e", "s/github.com/mannkind:$GITHUB_TOKEN@github.com/g", ".git/config"); err != nil {
		return err
	}

	if err := git("push", "--tags"); err != nil {
		return err
	}

	return nil
}

// Docker Build the docker images for each supported architecture
func (Docker) Build() error {
	for _, arch := range BinaryArchs {
		dockerfileWithArch := fmt.Sprintf("Dockerfile.%s", arch)
		dockerImageWithArch := fmt.Sprintf("%s:%s", DockerImage, arch)
		dockerImageWithArchVersion := fmt.Sprintf("%s-%s", dockerImageWithArch, BinaryVersion)
		dockerImageWithArchLatest := fmt.Sprintf("%s-latest", dockerImageWithArch)

		fmt.Println(fmt.Sprintf("Building image %s", dockerImageWithArchVersion))

		var golangArch = ""
		switch arch {
		case "amd64":
			golangArch = "amd64"
		case "arm32v6":
			golangArch = "arm"
		case "arm64v8":
			golangArch = "arm64"
		}

		if err := sh.Copy(dockerfileWithArch, "Dockerfile.template"); err != nil {
			return err
		}

		if err := sh.Run("sed", "-i", "-e", "s|__BASEIMAGE_ARCH__|"+arch+"|g", dockerfileWithArch); err != nil {
			return err
		}

		if err := sh.Run("sed", "-i", "-e", "s|__GOLANG_ARCH__|"+golangArch+"|g", dockerfileWithArch); err != nil {
			return err
		}

		if err := sh.Run("sed", "-i", "-e", "s|__BINARY_NAME__|"+BinaryName+"|g", dockerfileWithArch); err != nil {
			return err
		}

		if err := docker("build", "--pull", "-f", dockerfileWithArch, "-t", dockerImageWithArchVersion, "."); err != nil {
			return err
		}

		fmt.Println(fmt.Sprintf("Tagging image %s as %s", dockerImageWithArchVersion, dockerImageWithArchLatest))
		if err := docker("tag", dockerImageWithArchVersion, dockerImageWithArchLatest); err != nil {
			return err
		}
	}

	mg.SerialDeps(Clean)

	return nil
}

// Upload the docker image to Dockerhub for each supported architecture
func (Docker) Push() error {
	mg.SerialDeps(Docker.Build)

	tags := []string{BinaryVersion, "latest"}
	for _, tag := range tags {
		// Must push the images before they can be used in a manifest
		for _, arch := range BinaryArchs {
			dockerImageWithTag := fmt.Sprintf("%s:%s-%s", DockerImage, arch, tag)
			fmt.Println(fmt.Sprintf("Pushing image %s ", dockerImageWithTag))
			if err := docker("push", dockerImageWithTag); err != nil {
				return err
			}
		}

		dockerManifest := fmt.Sprintf("%s:%s", DockerImage, tag)

		// Create the manifest
		fmt.Println(fmt.Sprintf("Creating multi-arch manifest %s", dockerManifest))
		if err := docker("manifest", "create", dockerManifest, fmt.Sprintf("%s:%s-%s", DockerImage, BinaryArchs[0], tag), fmt.Sprintf("%s:%s-%s", DockerImage, BinaryArchs[1], tag), fmt.Sprintf("%s:%s-%s", DockerImage, BinaryArchs[2], tag)); err != nil {
			return err
		}

		// Annotate the manifest
		for _, arch := range BinaryArchs {
			fmt.Println(fmt.Sprintf("Annotating multi-arch manifest %s-%s", dockerManifest, arch))

			var archName = ""
			var archVariant = ""
			switch arch {
			case "amd64":
				archName = ""
				archVariant = ""
			case "arm32v6":
				archName = "arm"
				archVariant = "v6"
			case "arm64v8":
				archName = "arm64"
				archVariant = "v8"
			}

			if archName == "" && archVariant == "" {
				continue
			}

			if err := docker("manifest", "annotate", dockerManifest, fmt.Sprintf("%s:%s-%s", DockerImage, arch, tag), "--os", "linux", "--arch", archName, "--variant", archVariant); err != nil {
				return err
			}
		}

		// Publish the manifest
		fmt.Println(fmt.Sprintf("Pushing multi-arch manifest %s", dockerManifest))
		if err := docker("manifest", "push", "--purge", dockerManifest); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Remove all file system paths from the executable<commit_after>// +build mage

package main

import (
	"fmt"
	"time"

	"github.com/magefile/mage/mg"
	"github.com/magefile/mage/sh"
)

const (
	BaseBinaryVersion = "v0.15"
	BinaryName        = "mysensorsbootloader2mqtt"
	BaseDockerAccount = "mannkind"
)

// Everything below here is the same between projects
var BinaryArchs = []string{"amd64", "arm32v6", "arm64v8"}
var BinaryVersion = BaseBinaryVersion + time.Now().Format(".06002.1504")
var DockerImage = fmt.Sprintf("%s/%s", BaseDockerAccount, BinaryName)

// Commands
var g0 = sh.RunCmd("go")
var git = sh.RunCmd("git")
var docker = sh.RunCmd("docker")

type Go mg.Namespace
type Git mg.Namespace
type Docker mg.Namespace

// Default target to run when none is specified
var Default = All

// go:wire, go:format, go:vet, go:build, and go:test in that order
func All() {
	mg.SerialDeps(Go.Wire)
	mg.SerialDeps(Go.Format)
	mg.SerialDeps(Go.Vet)
	mg.SerialDeps(Go.Build)
	mg.SerialDeps(Go.Test)
	mg.SerialDeps(Go.Tidy)
}

// Remove the binary and architecture specific Dockerfiles
func Clean() error {
	fmt.Println("Cleaning")
	if err := sh.Run("rm", "-f", BinaryName); err != nil {
		return err
	}

	for _, arch := range BinaryArchs {
		if err := sh.Run("rm", "-f", "Dockerfile."+arch); err != nil {
			return err
		}
	}

	return nil
}

// Compile the application with the proper ldflags
func (Go) Build() error {
	fmt.Println("Building")
	return g0("build", "-trimpath", "-ldflags", "-X \"main.Version="+BinaryVersion+"\" -X \"main.Name="+BinaryName+"\"", "-o", BinaryName, ".")
}

// Ensure the code is formatted properly
func (Go) Format() error {
	fmt.Println("Formatting")
	return g0("fmt", ".")
}

// Ensure the code passes vetting
func (Go) Vet() error {
	fmt.Println("Vetting")
	return g0("vet", ".")
}

// Get the compile-time DI tool
func (Go) GetWire() error {
	fmt.Println("Getting Wire")
	return g0("get", "github.com/google/wire/cmd/wire")
}

// Generate the dependencies at compile-time
func (Go) Wire() error {
	mg.SerialDeps(Go.GetWire)

	fmt.Println("Wiring")
	return sh.Run("wire", "gen")
}

// Run the application tests
func (Go) Test() error {
	mg.SerialDeps(Go.Build)

	fmt.Println("Testing")
	return g0("test", "--coverprofile", "/tmp/app.cover", "-v", ".")
}

// Run the tidy command
func (Go) Tidy() error {
	mg.SerialDeps(Go.Build)

	fmt.Println("Tidying")
	return g0("mod", "tidy")
}

// Create a new tag for the git repository using the generated binary version
func (Git) Tag() error {
	fmt.Println("Tagging Git Repo")
	return git("tag", "-f", BinaryVersion)
}

// Create a realese in Github by pushing the tags in the git repository
func (Git) Push() error {
	mg.SerialDeps(Git.Tag)

	fmt.Println("Pushing Git Repo Tags")
	if err := sh.Run("sed", "-i", "-e", "s/github.com/mannkind:$GITHUB_TOKEN@github.com/g", ".git/config"); err != nil {
		return err
	}

	if err := git("push", "--tags"); err != nil {
		return err
	}

	return nil
}

// Docker Build the docker images for each supported architecture
func (Docker) Build() error {
	for _, arch := range BinaryArchs {
		dockerfileWithArch := fmt.Sprintf("Dockerfile.%s", arch)
		dockerImageWithArch := fmt.Sprintf("%s:%s", DockerImage, arch)
		dockerImageWithArchVersion := fmt.Sprintf("%s-%s", dockerImageWithArch, BinaryVersion)
		dockerImageWithArchLatest := fmt.Sprintf("%s-latest", dockerImageWithArch)

		fmt.Println(fmt.Sprintf("Building image %s", dockerImageWithArchVersion))

		var golangArch = ""
		switch arch {
		case "amd64":
			golangArch = "amd64"
		case "arm32v6":
			golangArch = "arm"
		case "arm64v8":
			golangArch = "arm64"
		}

		if err := sh.Copy(dockerfileWithArch, "Dockerfile.template"); err != nil {
			return err
		}

		if err := sh.Run("sed", "-i", "-e", "s|__BASEIMAGE_ARCH__|"+arch+"|g", dockerfileWithArch); err != nil {
			return err
		}

		if err := sh.Run("sed", "-i", "-e", "s|__GOLANG_ARCH__|"+golangArch+"|g", dockerfileWithArch); err != nil {
			return err
		}

		if err := sh.Run("sed", "-i", "-e", "s|__BINARY_NAME__|"+BinaryName+"|g", dockerfileWithArch); err != nil {
			return err
		}

		if err := docker("build", "--pull", "-f", dockerfileWithArch, "-t", dockerImageWithArchVersion, "."); err != nil {
			return err
		}

		fmt.Println(fmt.Sprintf("Tagging image %s as %s", dockerImageWithArchVersion, dockerImageWithArchLatest))
		if err := docker("tag", dockerImageWithArchVersion, dockerImageWithArchLatest); err != nil {
			return err
		}
	}

	mg.SerialDeps(Clean)

	return nil
}

// Upload the docker image to Dockerhub for each supported architecture
func (Docker) Push() error {
	mg.SerialDeps(Docker.Build)

	tags := []string{BinaryVersion, "latest"}
	for _, tag := range tags {
		// Must push the images before they can be used in a manifest
		for _, arch := range BinaryArchs {
			dockerImageWithTag := fmt.Sprintf("%s:%s-%s", DockerImage, arch, tag)
			fmt.Println(fmt.Sprintf("Pushing image %s ", dockerImageWithTag))
			if err := docker("push", dockerImageWithTag); err != nil {
				return err
			}
		}

		dockerManifest := fmt.Sprintf("%s:%s", DockerImage, tag)

		// Create the manifest
		fmt.Println(fmt.Sprintf("Creating multi-arch manifest %s", dockerManifest))
		if err := docker("manifest", "create", dockerManifest, fmt.Sprintf("%s:%s-%s", DockerImage, BinaryArchs[0], tag), fmt.Sprintf("%s:%s-%s", DockerImage, BinaryArchs[1], tag), fmt.Sprintf("%s:%s-%s", DockerImage, BinaryArchs[2], tag)); err != nil {
			return err
		}

		// Annotate the manifest
		for _, arch := range BinaryArchs {
			fmt.Println(fmt.Sprintf("Annotating multi-arch manifest %s-%s", dockerManifest, arch))

			var archName = ""
			var archVariant = ""
			switch arch {
			case "amd64":
				archName = ""
				archVariant = ""
			case "arm32v6":
				archName = "arm"
				archVariant = "v6"
			case "arm64v8":
				archName = "arm64"
				archVariant = "v8"
			}

			if archName == "" && archVariant == "" {
				continue
			}

			if err := docker("manifest", "annotate", dockerManifest, fmt.Sprintf("%s:%s-%s", DockerImage, arch, tag), "--os", "linux", "--arch", archName, "--variant", archVariant); err != nil {
				return err
			}
		}

		// Publish the manifest
		fmt.Println(fmt.Sprintf("Pushing multi-arch manifest %s", dockerManifest))
		if err := docker("manifest", "push", "--purge", dockerManifest); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package tester

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"

	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/go-getter"
	"github.com/mitchellh/cli"
)

func UpgradeCommandFactory() (cli.Command, error) {
	return &Upgrade{}, nil
}

type Upgrade struct {
}

func (c *Upgrade) Help() string {
	helpText := `
Usage consul-live upgrade base version1 ... versionN

  Starts Consul using the base executable then shuts it down and upgrades in
  place using the supplied version executables. The base version is populated
  with some test data and that data is verified after each upgrade.
`
	return strings.TrimSpace(helpText)
}

func (c *Upgrade) Synopsis() string {
	return "Runs Consul through a given series of in-place upgrades"
}

func (c *Upgrade) Run(args []string) int {
	if len(args) < 2 {
		log.Println("At least two versions must be given")
		return 1
	}

	if err := c.upgrade(args); err != nil {
		log.Println(err)
		return 1
	}
	return 0
}

type ServerConfig struct {
	Server           bool   `json:"server,omitempty"`
	Bootstrap        bool   `json:"bootstrap,omitempty"`
	Bind             string `json:"bind_addr,omitempty"`
	DataDir          string `json:"data_dir,omitempty"`
	Datacenter       string `json:"datacenter,omitempty"`
	ACLMasterToken   string `json:"acl_master_token,omitempty"`
	ACLDatacenter    string `json:"acl_datacenter,omitempty"`
	ACLDefaultPolicy string `json:"acl_default_policy,omitempty"`
	LogLevel         string `json:"log_level,omitempty"`
}

func (c *Upgrade) upgrade(versions []string) error {
	var dir string
	var err error
	dir, err = ioutil.TempDir("", "consul")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)

	from := path.Join(dir, "consul")
	for i, version := range versions {
		if err := getter.GetAny(dir, version); err != nil {
			return err
		}
		to := path.Join(dir, fmt.Sprintf("version-%d", i))
		if err := os.Rename(from, to); err != nil {
			return err
		}
		versions[i] = to
	}

	base := versions[0]
	versions = versions[1:]

	config, err := ioutil.TempFile(dir, "config")
	if err != nil {
		return err
	}

	content, err := json.Marshal(ServerConfig{
		Server:           true,
		Bootstrap:        true,
		Bind:             "127.0.0.1",
		DataDir:          dir,
		Datacenter:       "dc1",
		ACLMasterToken:   "root",
		ACLDatacenter:    "dc1",
		ACLDefaultPolicy: "allow",
	})
	if err != nil {
		return err
	}
	if _, err := config.Write(content); err != nil {
		return err
	}
	if err := config.Close(); err != nil {
		return err
	}

	target := config.Name() + ".json"
	if err := os.Rename(config.Name(), target); err != nil {
		return err
	}

	// Start the first version of Consul, which is our base.
	log.Printf("Starting base Consul from '%s'...\n", base)
	args := []string{
		"agent",
		"-config-file",
		target,
	}
	consul, err := NewConsul(base, args)
	if err != nil {
		return err
	}
	if err := consul.Start(); err != nil {
		return err
	}
	defer func() {
		if err := consul.Shutdown(); err != nil {
			log.Println(err)
		}
	}()

	// Wait for it to start up and elect itself.
	if err := consul.WaitForLeader(); err != nil {
		return err
	}

	// Populate it with some realistic data, enough to kick out a snapshot.
	log.Println("Populating with initial state store data...")
	client, err := api.NewClient(api.DefaultConfig())
	if err != nil {
		return err
	}
	fuzz, err := NewFuzz(client)
	if err != nil {
		return err
	}
	for {
		if err := fuzz.Populate(); err != nil {
			return err
		}

		entries, err := ioutil.ReadDir(dir + "/raft/snapshots/")
		if err != nil {
			return err
		}
		if len(entries) > 0 {
			break
		}
	}

	// Push some data in post-snapshot to make sure there's some stuff
	// in the Raft log as well.
	if err := fuzz.Populate(); err != nil {
		return err
	}
	if err := fuzz.Verify(); err != nil {
		return err
	}

	// Now shutdown the base version and try upgrading through the given
	// versions.
	if err := consul.Shutdown(); err != nil {
		return err
	}
	for _, version := range versions {
		// Start the upgraded version with the same data-dir.
		log.Printf("Upgrading to Consul from '%s'...\n", version)
		upgrade, err := NewConsul(version, args)
		if err != nil {
			return err
		}
		if err := upgrade.Start(); err != nil {
			return err
		}
		defer func() {
			if err := upgrade.Shutdown(); err != nil {
				log.Println(err)
			}
		}()

		// Wait for it to start up and elect itself.
		if err := upgrade.WaitForLeader(); err != nil {
			return err
		}

		// Make sure the data is still present.
		if err := fuzz.Verify(); err != nil {
			return err
		}

		// Add some new data for this version of Consul.
		if err := fuzz.Populate(); err != nil {
			return err
		}
		if err := fuzz.Verify(); err != nil {
			return err
		}

		// Shut it down in anticipation of the next upgrade.
		if err := upgrade.Shutdown(); err != nil {
			return err
		}
	}

	log.Println("Upgrade series complete")
	return nil
}
<commit_msg>Sets Raft protocol to 3 since we are doing a single-server upgrade.<commit_after>package tester

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"strings"

	"github.com/hashicorp/consul/api"
	"github.com/hashicorp/go-getter"
	"github.com/mitchellh/cli"
)

func UpgradeCommandFactory() (cli.Command, error) {
	return &Upgrade{}, nil
}

type Upgrade struct {
}

func (c *Upgrade) Help() string {
	helpText := `
Usage consul-live upgrade base version1 ... versionN

  Starts Consul using the base executable then shuts it down and upgrades in
  place using the supplied version executables. The base version is populated
  with some test data and that data is verified after each upgrade.
`
	return strings.TrimSpace(helpText)
}

func (c *Upgrade) Synopsis() string {
	return "Runs Consul through a given series of in-place upgrades"
}

func (c *Upgrade) Run(args []string) int {
	if len(args) < 2 {
		log.Println("At least two versions must be given")
		return 1
	}

	if err := c.upgrade(args); err != nil {
		log.Println(err)
		return 1
	}
	return 0
}

type ServerConfig struct {
	Server           bool   `json:"server,omitempty"`
	Bootstrap        bool   `json:"bootstrap,omitempty"`
	Bind             string `json:"bind_addr,omitempty"`
	DataDir          string `json:"data_dir,omitempty"`
	Datacenter       string `json:"datacenter,omitempty"`
	ACLMasterToken   string `json:"acl_master_token,omitempty"`
	ACLDatacenter    string `json:"acl_datacenter,omitempty"`
	ACLDefaultPolicy string `json:"acl_default_policy,omitempty"`
	RaftProtocol     int    `json:"raft_protocol,omitempty"`
	LogLevel         string `json:"log_level,omitempty"`
}

func (c *Upgrade) upgrade(versions []string) error {
	var dir string
	var err error
	dir, err = ioutil.TempDir("", "consul")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)

	from := path.Join(dir, "consul")
	for i, version := range versions {
		if err := getter.GetAny(dir, version); err != nil {
			return err
		}
		to := path.Join(dir, fmt.Sprintf("version-%d", i))
		if err := os.Rename(from, to); err != nil {
			return err
		}
		versions[i] = to
	}

	base := versions[0]
	versions = versions[1:]

	config, err := ioutil.TempFile(dir, "config")
	if err != nil {
		return err
	}

	content, err := json.Marshal(ServerConfig{
		Server:           true,
		Bootstrap:        true,
		Bind:             "127.0.0.1",
		DataDir:          dir,
		Datacenter:       "dc1",
		ACLMasterToken:   "root",
		ACLDatacenter:    "dc1",
		ACLDefaultPolicy: "allow",
		RaftProtocol:     3,
	})
	if err != nil {
		return err
	}
	if _, err := config.Write(content); err != nil {
		return err
	}
	if err := config.Close(); err != nil {
		return err
	}

	target := config.Name() + ".json"
	if err := os.Rename(config.Name(), target); err != nil {
		return err
	}

	// Start the first version of Consul, which is our base.
	log.Printf("Starting base Consul from '%s'...\n", base)
	args := []string{
		"agent",
		"-config-file",
		target,
	}
	consul, err := NewConsul(base, args)
	if err != nil {
		return err
	}
	if err := consul.Start(); err != nil {
		return err
	}
	defer func() {
		if err := consul.Shutdown(); err != nil {
			log.Println(err)
		}
	}()

	// Wait for it to start up and elect itself.
	if err := consul.WaitForLeader(); err != nil {
		return err
	}

	// Populate it with some realistic data, enough to kick out a snapshot.
	log.Println("Populating with initial state store data...")
	client, err := api.NewClient(api.DefaultConfig())
	if err != nil {
		return err
	}
	fuzz, err := NewFuzz(client)
	if err != nil {
		return err
	}
	for {
		if err := fuzz.Populate(); err != nil {
			return err
		}

		entries, err := ioutil.ReadDir(dir + "/raft/snapshots/")
		if err != nil {
			return err
		}
		if len(entries) > 0 {
			break
		}
	}

	// Push some data in post-snapshot to make sure there's some stuff
	// in the Raft log as well.
	if err := fuzz.Populate(); err != nil {
		return err
	}
	if err := fuzz.Verify(); err != nil {
		return err
	}

	// Now shutdown the base version and try upgrading through the given
	// versions.
	if err := consul.Shutdown(); err != nil {
		return err
	}
	for _, version := range versions {
		// Start the upgraded version with the same data-dir.
		log.Printf("Upgrading to Consul from '%s'...\n", version)
		upgrade, err := NewConsul(version, args)
		if err != nil {
			return err
		}
		if err := upgrade.Start(); err != nil {
			return err
		}
		defer func() {
			if err := upgrade.Shutdown(); err != nil {
				log.Println(err)
			}
		}()

		// Wait for it to start up and elect itself.
		if err := upgrade.WaitForLeader(); err != nil {
			return err
		}

		// Make sure the data is still present.
		if err := fuzz.Verify(); err != nil {
			return err
		}

		// Add some new data for this version of Consul.
		if err := fuzz.Populate(); err != nil {
			return err
		}
		if err := fuzz.Verify(); err != nil {
			return err
		}

		// Shut it down in anticipation of the next upgrade.
		if err := upgrade.Shutdown(); err != nil {
			return err
		}
	}

	log.Println("Upgrade series complete")
	return nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 Lawrence Woodman <lwoodman@vlifesystems.com>
 */
package rulehunter

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/lawrencewoodman/dexpr_go"
	"github.com/lawrencewoodman/rulehunter/internal"
	"os"
)

type Experiment struct {
	FileFormatVersion string
	Title             string
	Input             Input
	FieldNames        []string
	ExcludeFieldNames []string
	Aggregators       []internal.Aggregator
	Goals             []*dexpr.Expr
	SortOrder         []SortField
}

type SortField struct {
	Field     string
	Direction direction
}

type direction int

const (
	ASCENDING direction = iota
	DESCENDING
)

func (d direction) String() string {
	if d == ASCENDING {
		return "ascending"
	}
	return "descending"
}

type experimentFile struct {
	FileFormatVersion     string
	Title                 string
	InputFilename         string
	FieldNames            []string
	ExcludeFieldNames     []string
	IsFirstLineFieldNames bool
	Separator             string
	Aggregators           []experimentAggregator
	Goals                 []string
	SortOrder             []experimentSortField
}

type experimentAggregator struct {
	Name     string
	Function string
	Arg      string
}

type experimentSortField struct {
	AggregatorName string
	Direction      string
}

type ErrInvalidField struct {
	FieldName string
	Value     string
	Err       error
}

func (e *ErrInvalidField) Error() string {
	return fmt.Sprintf("Field: %q has Value: %q - %s", e.FieldName, e.Value, e.Err)
}

func LoadExperiment(filename string) (*Experiment, error) {
	var f *os.File
	var e experimentFile
	var experiment *Experiment
	var err error

	f, err = os.Open(filename)
	if err != nil {
		return nil, err
	}

	dec := json.NewDecoder(f)
	if err = dec.Decode(&e); err != nil {
		return nil, err
	}
	err = checkExperimentValid(e)
	if err != nil {
		return nil, err
	}
	experiment, err = makeExperiment(e)
	return experiment, err
}

func checkExperimentValid(e experimentFile) error {
	if e.FileFormatVersion == "" {
		return &ErrInvalidField{"fileFormatVersion", e.FileFormatVersion,
			errors.New("Must have a valid version number")}
	}
	// TODO: Test this more fully
	if len(e.FieldNames) < 2 {
		return &ErrInvalidField{"fieldNames",
			fmt.Sprintf("%q", e.FieldNames),
			errors.New("Must specify at least two field names")}
	}

	if len(e.Separator) != 1 {
		return &ErrInvalidField{"separator",
			fmt.Sprintf("%q", e.Separator),
			errors.New("Must contain one character only")}
	}
	return nil
}

func makeExperiment(e experimentFile) (*Experiment, error) {
	var goals []*dexpr.Expr
	var aggregators []internal.Aggregator
	var sortOrder []SortField
	var input Input
	var err error
	goals, err = makeGoals(e.Goals)
	if err != nil {
		return nil, err
	}
	aggregators, err = makeAggregators(e.Aggregators)
	if err != nil {
		return nil, err
	}

	sortOrder, err = makeSortOrder(e.SortOrder)
	if err != nil {
		return nil, err
	}

	input, err = internal.NewCsvInput(e.FieldNames, e.InputFilename,
		rune(e.Separator[0]), e.IsFirstLineFieldNames)
	if err != nil {
		return nil, err
	}

	return &Experiment{
		FileFormatVersion: e.FileFormatVersion,
		Title:             e.Title,
		Input:             input,
		FieldNames:        e.FieldNames,
		ExcludeFieldNames: e.ExcludeFieldNames,
		Aggregators:       aggregators,
		Goals:             goals,
		SortOrder:         sortOrder,
	}, nil
}

func makeGoal(expr string) (*dexpr.Expr, error) {
	r, err := dexpr.New(expr)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Can't make goal: %s", err))
	}
	return r, nil
}

func makeGoals(exprs []string) ([]*dexpr.Expr, error) {
	var err error
	r := make([]*dexpr.Expr, len(exprs))
	for i, s := range exprs {
		r[i], err = makeGoal(s)
		if err != nil {
			return r, err
		}
	}
	return r, nil
}

func makeAggregator(name, aggType, arg string) (internal.Aggregator, error) {
	var r internal.Aggregator
	var err error
	switch aggType {
	case "calc":
		r, err = internal.NewCalcAggregator(name, arg)
		return r, err
	case "count":
		r, err = internal.NewCountAggregator(name, arg)
		return r, err
	default:
		err = errors.New("Unrecognized aggregator")
	}
	if err != nil {
		// TODO: Make custome error type
		err = errors.New(fmt.Sprintf("Can't make aggregator: %s", err))
	}
	return r, err
}

func makeAggregators(
	eAggregators []experimentAggregator) ([]internal.Aggregator, error) {
	var err error
	r := make([]internal.Aggregator, len(eAggregators))
	for i, ea := range eAggregators {
		r[i], err = makeAggregator(ea.Name, ea.Function, ea.Arg)
		if err != nil {
			return r, err
		}
	}
	return r, nil
}

func makeSortOrder(eSortOrder []experimentSortField) ([]SortField, error) {
	r := make([]SortField, len(eSortOrder))
	for i, eSortField := range eSortOrder {
		field := eSortField.AggregatorName
		direction := eSortField.Direction
		// TODO: Make case insensitive
		if direction == "ascending" {
			r[i] = SortField{field, ASCENDING}
		} else if direction == "descending" {
			r[i] = SortField{field, DESCENDING}
		} else {
			err := errors.New(fmt.Sprintf("Invalid sort direction: %s, for field: %s",
				direction, field))
			return r, err
		}
	}
	return r, nil
}
<commit_msg>Put exported names at top of experiment.go<commit_after>/*
 * Copyright (C) 2016 Lawrence Woodman <lwoodman@vlifesystems.com>
 */
package rulehunter

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/lawrencewoodman/dexpr_go"
	"github.com/lawrencewoodman/rulehunter/internal"
	"os"
)

type Experiment struct {
	FileFormatVersion string
	Title             string
	Input             Input
	FieldNames        []string
	ExcludeFieldNames []string
	Aggregators       []internal.Aggregator
	Goals             []*dexpr.Expr
	SortOrder         []SortField
}

type SortField struct {
	Field     string
	Direction direction
}

type direction int

const (
	ASCENDING direction = iota
	DESCENDING
)

func (d direction) String() string {
	if d == ASCENDING {
		return "ascending"
	}
	return "descending"
}

type ErrInvalidField struct {
	FieldName string
	Value     string
	Err       error
}

func (e *ErrInvalidField) Error() string {
	return fmt.Sprintf("Field: %q has Value: %q - %s", e.FieldName, e.Value, e.Err)
}

func LoadExperiment(filename string) (*Experiment, error) {
	var f *os.File
	var e experimentFile
	var experiment *Experiment
	var err error

	f, err = os.Open(filename)
	if err != nil {
		return nil, err
	}

	dec := json.NewDecoder(f)
	if err = dec.Decode(&e); err != nil {
		return nil, err
	}
	err = checkExperimentValid(e)
	if err != nil {
		return nil, err
	}
	experiment, err = makeExperiment(e)
	return experiment, err
}

type experimentFile struct {
	FileFormatVersion     string
	Title                 string
	InputFilename         string
	FieldNames            []string
	ExcludeFieldNames     []string
	IsFirstLineFieldNames bool
	Separator             string
	Aggregators           []experimentAggregator
	Goals                 []string
	SortOrder             []experimentSortField
}

type experimentAggregator struct {
	Name     string
	Function string
	Arg      string
}

type experimentSortField struct {
	AggregatorName string
	Direction      string
}

func checkExperimentValid(e experimentFile) error {
	if e.FileFormatVersion == "" {
		return &ErrInvalidField{"fileFormatVersion", e.FileFormatVersion,
			errors.New("Must have a valid version number")}
	}
	// TODO: Test this more fully
	if len(e.FieldNames) < 2 {
		return &ErrInvalidField{"fieldNames",
			fmt.Sprintf("%q", e.FieldNames),
			errors.New("Must specify at least two field names")}
	}

	if len(e.Separator) != 1 {
		return &ErrInvalidField{"separator",
			fmt.Sprintf("%q", e.Separator),
			errors.New("Must contain one character only")}
	}
	return nil
}

func makeExperiment(e experimentFile) (*Experiment, error) {
	var goals []*dexpr.Expr
	var aggregators []internal.Aggregator
	var sortOrder []SortField
	var input Input
	var err error
	goals, err = makeGoals(e.Goals)
	if err != nil {
		return nil, err
	}
	aggregators, err = makeAggregators(e.Aggregators)
	if err != nil {
		return nil, err
	}

	sortOrder, err = makeSortOrder(e.SortOrder)
	if err != nil {
		return nil, err
	}

	input, err = internal.NewCsvInput(e.FieldNames, e.InputFilename,
		rune(e.Separator[0]), e.IsFirstLineFieldNames)
	if err != nil {
		return nil, err
	}

	return &Experiment{
		FileFormatVersion: e.FileFormatVersion,
		Title:             e.Title,
		Input:             input,
		FieldNames:        e.FieldNames,
		ExcludeFieldNames: e.ExcludeFieldNames,
		Aggregators:       aggregators,
		Goals:             goals,
		SortOrder:         sortOrder,
	}, nil
}

func makeGoal(expr string) (*dexpr.Expr, error) {
	r, err := dexpr.New(expr)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Can't make goal: %s", err))
	}
	return r, nil
}

func makeGoals(exprs []string) ([]*dexpr.Expr, error) {
	var err error
	r := make([]*dexpr.Expr, len(exprs))
	for i, s := range exprs {
		r[i], err = makeGoal(s)
		if err != nil {
			return r, err
		}
	}
	return r, nil
}

func makeAggregator(name, aggType, arg string) (internal.Aggregator, error) {
	var r internal.Aggregator
	var err error
	switch aggType {
	case "calc":
		r, err = internal.NewCalcAggregator(name, arg)
		return r, err
	case "count":
		r, err = internal.NewCountAggregator(name, arg)
		return r, err
	default:
		err = errors.New("Unrecognized aggregator")
	}
	if err != nil {
		// TODO: Make custome error type
		err = errors.New(fmt.Sprintf("Can't make aggregator: %s", err))
	}
	return r, err
}

func makeAggregators(
	eAggregators []experimentAggregator) ([]internal.Aggregator, error) {
	var err error
	r := make([]internal.Aggregator, len(eAggregators))
	for i, ea := range eAggregators {
		r[i], err = makeAggregator(ea.Name, ea.Function, ea.Arg)
		if err != nil {
			return r, err
		}
	}
	return r, nil
}

func makeSortOrder(eSortOrder []experimentSortField) ([]SortField, error) {
	r := make([]SortField, len(eSortOrder))
	for i, eSortField := range eSortOrder {
		field := eSortField.AggregatorName
		direction := eSortField.Direction
		// TODO: Make case insensitive
		if direction == "ascending" {
			r[i] = SortField{field, ASCENDING}
		} else if direction == "descending" {
			r[i] = SortField{field, DESCENDING}
		} else {
			err := errors.New(fmt.Sprintf("Invalid sort direction: %s, for field: %s",
				direction, field))
			return r, err
		}
	}
	return r, nil
}
<|endoftext|>


<commit_before>package extra

import (
	"fmt"
	"os/exec"

	"github.com/elpinal/coco3/extra/ast"
	"github.com/elpinal/coco3/extra/typed"
)

type Env struct {
	cmds map[string]typed.Command
}

func New() Env {
	return Env{cmds: map[string]typed.Command{
		"exec": execCommand,
	}}
}

func WithoutDefault() Env {
	return Env{cmds: make(map[string]typed.Command)}
}

func (e *Env) Bind(name string, c typed.Command) {
	e.cmds[name] = c
}

func (e *Env) Eval(command *ast.Command) error {
	tc, found := e.cmds[command.Name]
	if !found {
		return fmt.Errorf("no such typed command: %q", command.Name)
	}
	if len(command.Args) != len(tc.Params) {
		return fmt.Errorf("the length of args (%d) != the one of params (%d)", len(command.Args), len(tc.Params))
	}
	args := make([]string, 0, len(command.Args))
	for i, arg := range command.Args {
		if arg.Kind != tc.Params[i] {
			return fmt.Errorf("type mismatch: %v != %v", arg.Kind, tc.Params[i])
		}
		args = append(args, arg.Lit)
	}
	return tc.Fn(args)
}

var execCommand = typed.Command{
	Params: []typed.Type{typed.String},
	Fn: func(args []string) error {
		return exec.Command(args[0]).Run()
	},
}
<commit_msg>Add 'cd' typed command<commit_after>package extra

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/elpinal/coco3/extra/ast"
	"github.com/elpinal/coco3/extra/typed"
)

type Env struct {
	cmds map[string]typed.Command
}

func New() Env {
	return Env{cmds: map[string]typed.Command{
		"exec": execCommand,
		"cd":   cdCommand,
	}}
}

func WithoutDefault() Env {
	return Env{cmds: make(map[string]typed.Command)}
}

func (e *Env) Bind(name string, c typed.Command) {
	e.cmds[name] = c
}

func (e *Env) Eval(command *ast.Command) error {
	tc, found := e.cmds[command.Name]
	if !found {
		return fmt.Errorf("no such typed command: %q", command.Name)
	}
	if len(command.Args) != len(tc.Params) {
		return fmt.Errorf("the length of args (%d) != the one of params (%d)", len(command.Args), len(tc.Params))
	}
	args := make([]string, 0, len(command.Args))
	for i, arg := range command.Args {
		if arg.Kind != tc.Params[i] {
			return fmt.Errorf("type mismatch: %v != %v", arg.Kind, tc.Params[i])
		}
		args = append(args, arg.Lit)
	}
	return tc.Fn(args)
}

var execCommand = typed.Command{
	Params: []typed.Type{typed.String},
	Fn: func(args []string) error {
		return exec.Command(args[0]).Run()
	},
}

var cdCommand = typed.Command{
	Params: []typed.Type{typed.String},
	Fn: func(args []string) error {
		return os.Chdir(args[0])
	},
}
<|endoftext|>


<commit_before>package lifecycle

import (
	"crypto/sha256"
	"encoding/hex"
	"strconv"
	"strings"

	"github.com/cozy/cozy-stack/model/contact"
	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/job"
	"github.com/cozy/cozy-stack/pkg/config/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/crypto"
	"github.com/cozy/cozy-stack/pkg/hooks"
	"github.com/cozy/cozy-stack/pkg/prefixer"
	"github.com/cozy/cozy-stack/pkg/utils"
)

// Options holds the parameters to create a new instance.
type Options struct {
	Domain         string
	DomainAliases  []string
	Locale         string
	UUID           string
	OIDCID         string
	TOSSigned      string
	TOSLatest      string
	Timezone       string
	ContextName    string
	Email          string
	PublicName     string
	Settings       string
	SettingsObj    *couchdb.JSONDoc
	AuthMode       string
	Passphrase     string
	Key            string
	KdfIterations  int
	SwiftLayout    int
	DiskQuota      int64
	Apps           []string
	AutoUpdate     *bool
	Debug          *bool
	Deleting       *bool
	Blocked        *bool
	BlockingReason string

	OnboardingFinished *bool
}

// Create builds an instance and initializes it
func Create(opts *Options) (*instance.Instance, error) {
	domain, err := validateDomain(opts.Domain)
	if err != nil {
		return nil, err
	}
	var inst *instance.Instance
	err = hooks.Execute("add-instance", []string{domain}, func() error {
		var err2 error
		inst, err2 = CreateWithoutHooks(opts)
		return err2
	})
	return inst, err
}

// CreateWithoutHooks builds an instance and initializes it. The difference
// with Create is that script hooks are not executed for this function.
func CreateWithoutHooks(opts *Options) (*instance.Instance, error) {
	domain, err := validateDomain(opts.Domain)
	if err != nil {
		return nil, err
	}
	if _, err = instance.GetFromCouch(domain); err != instance.ErrNotFound {
		if err == nil {
			err = instance.ErrExists
		}
		return nil, err
	}

	locale := opts.Locale
	if locale == "" {
		locale = consts.DefaultLocale
	}

	settings := buildSettings(opts)
	prefix := sha256.Sum256([]byte(domain))
	i := &instance.Instance{}
	i.Domain = domain
	i.DomainAliases, err = checkAliases(i, opts.DomainAliases)
	if err != nil {
		return nil, err
	}
	i.Prefix = "cozy" + hex.EncodeToString(prefix[:16])
	i.Locale = locale
	i.UUID = opts.UUID
	i.OIDCID = opts.OIDCID
	i.TOSSigned = opts.TOSSigned
	i.TOSLatest = opts.TOSLatest
	i.ContextName = opts.ContextName
	i.BytesDiskQuota = opts.DiskQuota
	i.IndexViewsVersion = couchdb.IndexViewsVersion
	i.RegisterToken = crypto.GenerateRandomBytes(instance.RegisterTokenLen)
	i.SessSecret = crypto.GenerateRandomBytes(instance.SessionSecretLen)
	i.OAuthSecret = crypto.GenerateRandomBytes(instance.OauthSecretLen)
	i.CLISecret = crypto.GenerateRandomBytes(instance.OauthSecretLen)

	if 0 <= opts.SwiftLayout && opts.SwiftLayout <= 2 {
		i.SwiftLayout = opts.SwiftLayout
	} else {
		i.SwiftLayout = config.GetConfig().Fs.DefaultLayout
	}

	if opts.AuthMode != "" {
		var authMode instance.AuthMode
		if authMode, err = instance.StringToAuthMode(opts.AuthMode); err == nil {
			i.AuthMode = authMode
		}
	}

	// If the authentication is disabled, we force a random password. It won't
	// be known by the user and cannot be used to authenticate. It will only be
	// used if the configuration is changed later: the user will be able to
	// reset the passphrase.
	if !i.IsPasswordAuthenticationEnabled() {
		opts.Passphrase = utils.RandomString(instance.RegisterTokenLen)
		opts.KdfIterations = crypto.DefaultPBKDF2Iterations
	}

	if opts.Passphrase != "" {
		err = registerPassphrase(i, i.RegisterToken, PassParameters{
			Pass:       []byte(opts.Passphrase),
			Iterations: opts.KdfIterations,
			Key:        opts.Key,
		})
		if err != nil {
			return nil, err
		}
		// set the onboarding finished when specifying a passphrase. we totally
		// skip the onboarding in that case.
		i.OnboardingFinished = true
	}

	if onboardingFinished := opts.OnboardingFinished; onboardingFinished != nil {
		i.OnboardingFinished = *onboardingFinished
	}

	if autoUpdate := opts.AutoUpdate; autoUpdate != nil {
		i.NoAutoUpdate = !(*opts.AutoUpdate)
	}

	if err := couchdb.CreateDoc(couchdb.GlobalDB, i); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Files); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Apps); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Konnectors); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.OAuthClients); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Permissions); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Sharings); err != nil {
		return nil, err
	}
	if err := i.MakeVFS(); err != nil {
		return nil, err
	}
	if err := i.VFS().InitFs(); err != nil {
		return nil, err
	}
	if err := couchdb.CreateNamedDocWithDB(i, settings); err != nil {
		return nil, err
	}
	if err := DefineViewsAndIndex(i); err != nil {
		return nil, err
	}
	if err := createDefaultFilesTree(i); err != nil {
		return nil, err
	}
	if _, err := contact.CreateMyself(i, settings); err != nil {
		return nil, err
	}
	sched := job.System()
	for _, trigger := range Triggers(i) {
		t, err := job.NewTrigger(i, trigger, nil)
		if err != nil {
			return nil, err
		}
		if err = sched.AddTrigger(t); err != nil {
			return nil, err
		}
	}
	for _, app := range opts.Apps {
		if err := installApp(i, app); err != nil {
			i.Logger().Errorf("Failed to install %s: %s", app, err)
		}
	}
	return i, nil
}

func buildSettings(opts *Options) *couchdb.JSONDoc {
	var settings *couchdb.JSONDoc
	if opts.SettingsObj != nil {
		settings = opts.SettingsObj
	} else {
		settings = &couchdb.JSONDoc{M: make(map[string]interface{})}
	}

	settings.Type = consts.Settings
	settings.SetID(consts.InstanceSettingsID)

	for _, s := range strings.Split(opts.Settings, ",") {
		if parts := strings.SplitN(s, ":", 2); len(parts) == 2 {
			settings.M[parts[0]] = parts[1]
		}
	}

	// Handling global/instance settings
	if contextName, ok := settings.M["context"].(string); ok {
		opts.ContextName = contextName
		delete(settings.M, "context")
	}
	if locale, ok := settings.M["locale"].(string); ok {
		opts.Locale = locale
		delete(settings.M, "locale")
	}
	if onboardingFinished, ok := settings.M["onboarding_finished"].(bool); ok {
		opts.OnboardingFinished = &onboardingFinished
		delete(settings.M, "onboarding_finished")
	}
	if uuid, ok := settings.M["uuid"].(string); ok {
		opts.UUID = uuid
		delete(settings.M, "uuid")
	}
	if oidcID, ok := settings.M["oidc_id"].(string); ok {
		opts.OIDCID = oidcID
		delete(settings.M, "oidc_id")
	}
	if tos, ok := settings.M["tos"].(string); ok {
		opts.TOSSigned = tos
		delete(settings.M, "tos")
	}
	if tos, ok := settings.M["tos_latest"].(string); ok {
		opts.TOSLatest = tos
		delete(settings.M, "tos_latest")
	}
	if autoUpdate, ok := settings.M["auto_update"].(string); ok {
		if b, err := strconv.ParseBool(autoUpdate); err == nil {
			opts.AutoUpdate = &b
		}
		delete(settings.M, "auto_update")
	}
	if authMode, ok := settings.M["auth_mode"].(string); ok {
		opts.AuthMode = authMode
		delete(settings.M, "auth_mode")
	}

	// Handling instance settings document
	if tz := opts.Timezone; tz != "" {
		settings.M["tz"] = tz
	}
	if email := opts.Email; email != "" {
		settings.M["email"] = email
	}
	if name := opts.PublicName; name != "" {
		settings.M["public_name"] = name
	}

	if len(opts.TOSSigned) == 8 {
		opts.TOSSigned = "1.0.0-" + opts.TOSSigned
	}

	return settings
}

// Triggers returns the list of the triggers to add when an instance is created
func Triggers(db prefixer.Prefixer) []job.TriggerInfos {
	// Create/update/remove thumbnails when an image is created/updated/removed
	return []job.TriggerInfos{
		{
			Domain:     db.DomainName(),
			Prefix:     db.DBPrefix(),
			Type:       "@event",
			WorkerType: "thumbnail",
			Arguments:  "io.cozy.files:CREATED,UPDATED,DELETED:image:class",
		},
	}
}
<commit_msg>Disable instance creation using swift layout v1<commit_after>package lifecycle

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"strconv"
	"strings"

	"github.com/cozy/cozy-stack/model/contact"
	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/job"
	"github.com/cozy/cozy-stack/pkg/config/config"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/crypto"
	"github.com/cozy/cozy-stack/pkg/hooks"
	"github.com/cozy/cozy-stack/pkg/prefixer"
	"github.com/cozy/cozy-stack/pkg/utils"
)

// Options holds the parameters to create a new instance.
type Options struct {
	Domain         string
	DomainAliases  []string
	Locale         string
	UUID           string
	OIDCID         string
	TOSSigned      string
	TOSLatest      string
	Timezone       string
	ContextName    string
	Email          string
	PublicName     string
	Settings       string
	SettingsObj    *couchdb.JSONDoc
	AuthMode       string
	Passphrase     string
	Key            string
	KdfIterations  int
	SwiftLayout    int
	DiskQuota      int64
	Apps           []string
	AutoUpdate     *bool
	Debug          *bool
	Deleting       *bool
	Blocked        *bool
	BlockingReason string

	OnboardingFinished *bool
}

// Create builds an instance and initializes it
func Create(opts *Options) (*instance.Instance, error) {
	domain, err := validateDomain(opts.Domain)
	if err != nil {
		return nil, err
	}
	var inst *instance.Instance
	err = hooks.Execute("add-instance", []string{domain}, func() error {
		var err2 error
		inst, err2 = CreateWithoutHooks(opts)
		return err2
	})
	return inst, err
}

// CreateWithoutHooks builds an instance and initializes it. The difference
// with Create is that script hooks are not executed for this function.
func CreateWithoutHooks(opts *Options) (*instance.Instance, error) {
	domain, err := validateDomain(opts.Domain)
	if err != nil {
		return nil, err
	}
	if _, err = instance.GetFromCouch(domain); err != instance.ErrNotFound {
		if err == nil {
			err = instance.ErrExists
		}
		return nil, err
	}

	locale := opts.Locale
	if locale == "" {
		locale = consts.DefaultLocale
	}

	settings := buildSettings(opts)
	prefix := sha256.Sum256([]byte(domain))
	i := &instance.Instance{}
	i.Domain = domain
	i.DomainAliases, err = checkAliases(i, opts.DomainAliases)
	if err != nil {
		return nil, err
	}
	i.Prefix = "cozy" + hex.EncodeToString(prefix[:16])
	i.Locale = locale
	i.UUID = opts.UUID
	i.OIDCID = opts.OIDCID
	i.TOSSigned = opts.TOSSigned
	i.TOSLatest = opts.TOSLatest
	i.ContextName = opts.ContextName
	i.BytesDiskQuota = opts.DiskQuota
	i.IndexViewsVersion = couchdb.IndexViewsVersion
	i.RegisterToken = crypto.GenerateRandomBytes(instance.RegisterTokenLen)
	i.SessSecret = crypto.GenerateRandomBytes(instance.SessionSecretLen)
	i.OAuthSecret = crypto.GenerateRandomBytes(instance.OauthSecretLen)
	i.CLISecret = crypto.GenerateRandomBytes(instance.OauthSecretLen)

	switch opts.SwiftLayout {
	case 0:
		err = errors.New("Swift layout v1 disabled for instance creation")
		return nil, err
	case 1, 2:
		i.SwiftLayout = opts.SwiftLayout
	default:
		i.SwiftLayout = config.GetConfig().Fs.DefaultLayout
	}

	if opts.AuthMode != "" {
		var authMode instance.AuthMode
		if authMode, err = instance.StringToAuthMode(opts.AuthMode); err == nil {
			i.AuthMode = authMode
		}
	}

	// If the authentication is disabled, we force a random password. It won't
	// be known by the user and cannot be used to authenticate. It will only be
	// used if the configuration is changed later: the user will be able to
	// reset the passphrase.
	if !i.IsPasswordAuthenticationEnabled() {
		opts.Passphrase = utils.RandomString(instance.RegisterTokenLen)
		opts.KdfIterations = crypto.DefaultPBKDF2Iterations
	}

	if opts.Passphrase != "" {
		err = registerPassphrase(i, i.RegisterToken, PassParameters{
			Pass:       []byte(opts.Passphrase),
			Iterations: opts.KdfIterations,
			Key:        opts.Key,
		})
		if err != nil {
			return nil, err
		}
		// set the onboarding finished when specifying a passphrase. we totally
		// skip the onboarding in that case.
		i.OnboardingFinished = true
	}

	if onboardingFinished := opts.OnboardingFinished; onboardingFinished != nil {
		i.OnboardingFinished = *onboardingFinished
	}

	if autoUpdate := opts.AutoUpdate; autoUpdate != nil {
		i.NoAutoUpdate = !(*opts.AutoUpdate)
	}

	if err := couchdb.CreateDoc(couchdb.GlobalDB, i); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Files); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Apps); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Konnectors); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.OAuthClients); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Permissions); err != nil {
		return nil, err
	}
	if err := couchdb.CreateDB(i, consts.Sharings); err != nil {
		return nil, err
	}
	if err := i.MakeVFS(); err != nil {
		return nil, err
	}
	if err := i.VFS().InitFs(); err != nil {
		return nil, err
	}
	if err := couchdb.CreateNamedDocWithDB(i, settings); err != nil {
		return nil, err
	}
	if err := DefineViewsAndIndex(i); err != nil {
		return nil, err
	}
	if err := createDefaultFilesTree(i); err != nil {
		return nil, err
	}
	if _, err := contact.CreateMyself(i, settings); err != nil {
		return nil, err
	}
	sched := job.System()
	for _, trigger := range Triggers(i) {
		t, err := job.NewTrigger(i, trigger, nil)
		if err != nil {
			return nil, err
		}
		if err = sched.AddTrigger(t); err != nil {
			return nil, err
		}
	}
	for _, app := range opts.Apps {
		if err := installApp(i, app); err != nil {
			i.Logger().Errorf("Failed to install %s: %s", app, err)
		}
	}
	return i, nil
}

func buildSettings(opts *Options) *couchdb.JSONDoc {
	var settings *couchdb.JSONDoc
	if opts.SettingsObj != nil {
		settings = opts.SettingsObj
	} else {
		settings = &couchdb.JSONDoc{M: make(map[string]interface{})}
	}

	settings.Type = consts.Settings
	settings.SetID(consts.InstanceSettingsID)

	for _, s := range strings.Split(opts.Settings, ",") {
		if parts := strings.SplitN(s, ":", 2); len(parts) == 2 {
			settings.M[parts[0]] = parts[1]
		}
	}

	// Handling global/instance settings
	if contextName, ok := settings.M["context"].(string); ok {
		opts.ContextName = contextName
		delete(settings.M, "context")
	}
	if locale, ok := settings.M["locale"].(string); ok {
		opts.Locale = locale
		delete(settings.M, "locale")
	}
	if onboardingFinished, ok := settings.M["onboarding_finished"].(bool); ok {
		opts.OnboardingFinished = &onboardingFinished
		delete(settings.M, "onboarding_finished")
	}
	if uuid, ok := settings.M["uuid"].(string); ok {
		opts.UUID = uuid
		delete(settings.M, "uuid")
	}
	if oidcID, ok := settings.M["oidc_id"].(string); ok {
		opts.OIDCID = oidcID
		delete(settings.M, "oidc_id")
	}
	if tos, ok := settings.M["tos"].(string); ok {
		opts.TOSSigned = tos
		delete(settings.M, "tos")
	}
	if tos, ok := settings.M["tos_latest"].(string); ok {
		opts.TOSLatest = tos
		delete(settings.M, "tos_latest")
	}
	if autoUpdate, ok := settings.M["auto_update"].(string); ok {
		if b, err := strconv.ParseBool(autoUpdate); err == nil {
			opts.AutoUpdate = &b
		}
		delete(settings.M, "auto_update")
	}
	if authMode, ok := settings.M["auth_mode"].(string); ok {
		opts.AuthMode = authMode
		delete(settings.M, "auth_mode")
	}

	// Handling instance settings document
	if tz := opts.Timezone; tz != "" {
		settings.M["tz"] = tz
	}
	if email := opts.Email; email != "" {
		settings.M["email"] = email
	}
	if name := opts.PublicName; name != "" {
		settings.M["public_name"] = name
	}

	if len(opts.TOSSigned) == 8 {
		opts.TOSSigned = "1.0.0-" + opts.TOSSigned
	}

	return settings
}

// Triggers returns the list of the triggers to add when an instance is created
func Triggers(db prefixer.Prefixer) []job.TriggerInfos {
	// Create/update/remove thumbnails when an image is created/updated/removed
	return []job.TriggerInfos{
		{
			Domain:     db.DomainName(),
			Prefix:     db.DBPrefix(),
			Type:       "@event",
			WorkerType: "thumbnail",
			Arguments:  "io.cozy.files:CREATED,UPDATED,DELETED:image:class",
		},
	}
}
<|endoftext|>


<commit_before>// Copyright 2020, 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://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 runner_test

import (
	"encoding/xml"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/phst/runfiles"
)

func Test(t *testing.T) {
	source, err := runfiles.Path("phst_rules_elisp/tests/test.el")
	if err != nil {
		t.Fatal(err)
	}
	// Use the ancestor of the source file as workspace directory so that
	// filenames in the coverage report are correct.
	workspace := filepath.Dir(filepath.Dir(source))
	t.Logf("running test in workspace directory %s", workspace)
	bin := "phst_rules_elisp/tests/test_test"
	if runtime.GOOS == "windows" {
		bin += ".exe"
	}
	bin, err = runfiles.Path(bin)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("using test binary %s", bin)
	runfilesEnv, err := runfiles.Env()
	if err != nil {
		t.Fatal(err)
	}
	tempDir := t.TempDir()
	reportName := filepath.Join(tempDir, "report.xml")
	coverageManifest := filepath.Join(tempDir, "coverage-manifest.txt")
	t.Logf("writing coverage manifest %s", coverageManifest)
	if err := os.WriteFile(coverageManifest, []byte("tests/test-lib.el\nunrelated.el\n"), 0400); err != nil {
		t.Error(err)
	}
	coverageDir := t.TempDir()

	cmd := exec.Command(bin, "arg 1", "arg\n2")
	// See
	// https://docs.bazel.build/versions/4.1.0/test-encyclopedia.html#initial-conditions.
	cmd.Env = append(os.Environ(), append(runfilesEnv,
		"XML_OUTPUT_FILE="+reportName,
		"TESTBRIDGE_TEST_ONLY=(not (tag skip))",
		"COVERAGE=1",
		"COVERAGE_MANIFEST="+coverageManifest,
		"COVERAGE_DIR="+coverageDir)...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = workspace
	switch err := cmd.Run().(type) {
	case nil:
		t.Error("test binary succeeded unexpectedly")
	case *exec.ExitError:
		if err.ExitCode() != 1 {
			t.Errorf("test binary: got exit code %d, want 1", err.ExitCode())
		}
	default:
		t.Error(err)
	}
	t.Log("test process exited")

	schema, err := runfiles.Path("junit_xsd/file/JUnit.xsd")
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("validing XML report %s against schema %s", reportName, schema)
	cmd = exec.Command("xmllint", "--nonet", "--noout", "--schema", schema, reportName)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		t.Errorf("error validating XML report file: %s", err)
	}
	t.Log("XML validation complete")

	t.Log("parsing XML report")
	b, err := ioutil.ReadFile(reportName)
	if err != nil {
		t.Fatal(err)
	}
	type message struct {
		Message     string `xml:"message,attr"`
		Type        string `xml:"type,attr"`
		Description string `xml:",chardata"`
	}
	isDescription := func(p cmp.Path) bool {
		f, ok := p.Last().(cmp.StructField)
		return ok && f.Name() == "Description"
	}
	type property struct {
		Name  string `xml:"name,attr"`
		Value string `xml:"value,attr"`
	}
	type properties struct {
		Properties []property `xml:"property"`
	}
	type testCase struct {
		Name      string    `xml:"name,attr"`
		ClassName string    `xml:"classname,attr"`
		Time      float64   `xml:"time,attr"`
		Skipped   *struct{} `xml:"skipped"`
		Error     message   `xml:"error"`
		Failure   message   `xml:"failure"`
	}
	type report struct {
		XMLName    xml.Name
		Name       string     `xml:"name,attr"`
		Tests      int        `xml:"tests,attr"`
		Errors     int        `xml:"errors,attr"`
		Failures   int        `xml:"failures,attr"`
		Skipped    int        `xml:"skipped,attr"`
		Time       float64    `xml:"time,attr"`
		Timestamp  timestamp  `xml:"timestamp,attr"`
		Properties properties `xml:"properties"`
		TestCases  []testCase `xml:"testcase"`
	}
	var gotReport report
	if err := xml.Unmarshal(b, &gotReport); err != nil {
		t.Error(err)
	}
	var emacsVersion string
	for _, prop := range gotReport.Properties.Properties {
		if prop.Name == "emacs-version" {
			emacsVersion = prop.Value
			break
		}
	}
	if !regexp.MustCompile(`^\d+\.\d+`).MatchString(emacsVersion) {
		t.Errorf("invalid Emacs version %q", emacsVersion)
	}
	t.Logf("got Emacs version %s", emacsVersion)
	// Margin for time comparisons.  One hour is excessive, but we only
	// care about catching obvious bugs here.
	const margin = time.Hour
	// This, together with the EquateApprox below, ensures that the elapsed
	// time is nonnegative and below the margin.
	wantElapsed := margin.Seconds() / 2
	wantReport := report{
		XMLName:    xml.Name{"", "testsuite"},
		Name:       "ERT",
		Tests:      12,
		Errors:     0,
		Failures:   7,
		Skipped:    1,
		Time:       wantElapsed,
		Timestamp:  timestamp(time.Now()),
		Properties: properties{[]property{{"emacs-version", emacsVersion}}},
		TestCases: []testCase{
			{
				Name: "abort", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `peculiar error: "Boo"`, Type: `undefined-error-symbol`, Description: "something"},
			},
			{Name: "command-line", ClassName: "ERT", Time: wantElapsed},
			{Name: "coverage", ClassName: "ERT", Time: wantElapsed},
			{
				Name: "error", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `Boo`, Type: `error`, Description: "something"},
			},
			{
				Name: "ert-fail", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `Test failed: "Fail!"`, Type: `ert-test-failed`, Description: "something"},
			},
			{Name: "expect-failure", ClassName: "ERT", Time: wantElapsed},
			{
				Name: "expect-failure-but-pass", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `Test passed unexpectedly`, Type: `error`},
			},
			{
				Name: "fail", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `Test failed: ((should (= 0 1)) :form (= 0 1) :value nil)`, Type: `ert-test-failed`, Description: "something"},
			},
			{Name: "pass", ClassName: "ERT", Time: wantElapsed},
			{Name: "skip", ClassName: "ERT", Time: wantElapsed, Skipped: new(struct{})},
			{
				Name: "special-chars", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: "Error äöü \t \n \\u0000 \uFFFD \\uFFFE \\uFFFF 𝑨 <![CDATA[ ]]> & < > \" ' <!-- -->", Type: `error`, Description: "something"},
			},
			{
				Name: "throw", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `No catch for tag: unknown-tag, hi`, Type: `no-catch`, Description: "something"},
			},
		},
	}
	if diff := cmp.Diff(
		gotReport, wantReport,
		cmp.Transformer("time.Time", toTime), cmpopts.EquateApprox(0, wantElapsed), cmpopts.EquateApproxTime(margin),
		// We only check that the description isn’t absent or empty.
		cmp.FilterPath(isDescription, cmp.Comparer(bothEmpty)),
	); diff != "" {
		t.Error("XML test report (-got +want):\n", diff)
	}

	t.Logf("looking for coverage files in %s", coverageDir)
	files, err := filepath.Glob(filepath.Join(coverageDir, "*.dat"))
	if err != nil {
		t.Error(err)
	}
	if len(files) != 1 {
		t.Fatalf("got %d coverage files %v, want exactly one", len(files), files)
	}
	b, err = ioutil.ReadFile(files[0])
	if err != nil {
		t.Error(err)
	}
	gotCoverage := string(b)
	// See geninfo(1) for the coverage file format.  The function hit count
	// doesn’t work yet for nested functions.  The jump in the suffix index
	// for the nested functions is due to
	// https://debbugs.gnu.org/cgi/bugreport.cgi?bug=41988.
	wantCoverage := `SF:tests/test-lib.el
FN:27,tests/test-function
FN:39,foo@cl-flet@1
FN:41,foo@cl-flet@3
FNDA:1,tests/test-function
FNDA:0,foo@cl-flet@1
FNDA:0,foo@cl-flet@3
FNF:3
FNH:1
BRDA:30,0,0,0
BRDA:30,0,1,1
BRDA:34,0,0,1
BRDA:34,0,1,0
BRDA:44,0,0,0
BRDA:44,0,1,1
BRDA:44,0,2,0
BRDA:44,0,3,0
BRDA:46,0,0,0
BRDA:46,0,1,1
BRDA:46,0,2,1
BRDA:46,0,3,0
BRDA:46,1,0,1
BRDA:46,1,1,0
BRDA:48,0,0,-
BRDA:48,0,1,-
BRDA:49,0,0,1
BRDA:49,0,1,0
BRDA:53,0,0,0
BRDA:53,0,1,1
BRDA:53,0,2,0
BRDA:53,0,3,0
BRDA:54,0,0,3
BRDA:54,0,1,0
BRF:24
BRH:9
DA:30,1
DA:31,1
DA:32,0
DA:33,1
DA:34,1
DA:35,1
DA:36,1
DA:37,0
DA:39,1
DA:40,1
DA:41,1
DA:42,1
DA:44,1
DA:45,1
DA:46,1
DA:47,0
DA:48,0
DA:49,1
DA:50,1
DA:51,0
DA:52,1
DA:53,1
DA:54,3
DA:56,1
LH:19
LF:24
end_of_record
`
	if strings.HasPrefix(emacsVersion, "26.") {
		// No branch coverage under Emacs 26 expected.
		t.Log("not checking for branch coverage")
		wantCoverage = regexp.MustCompile(`(?m)^BR.+\n`).ReplaceAllLiteralString(wantCoverage, "")
	}
	// Depending on the exact runfiles layout, the test runner might have
	// printed different representations of test filenames.
	gotCoverage = regexp.MustCompile(`(?m)^(SF:).+[/\\](tests[/\\]test-lib\.el)$`).ReplaceAllString(gotCoverage, "$1$2")
	if diff := cmp.Diff(gotCoverage, wantCoverage); diff != "" {
		t.Error("coverage report (-got +want):\n", diff)
	}
}

type timestamp time.Time

func (t *timestamp) UnmarshalText(b []byte) error {
	// The XML report format doesn’t allow timezones in timestamps, so
	// time.Time.UnmarshalText doesn’t work.
	u, err := time.Parse("2006-01-02T15:04:05", string(b))
	*t = timestamp(u)
	return err
}

func toTime(t timestamp) time.Time { return time.Time(t) }

func bothEmpty(a, b string) bool { return (a == "") == (b == "") }
<commit_msg>Don’t allow the Python launcher to change the current directory<commit_after>// Copyright 2020, 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://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 runner_test

import (
	"encoding/xml"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/phst/runfiles"
)

func Test(t *testing.T) {
	source, err := runfiles.Path("phst_rules_elisp/tests/test.el")
	if err != nil {
		t.Fatal(err)
	}
	// Use the ancestor of the source file as workspace directory so that
	// filenames in the coverage report are correct.
	workspace := filepath.Dir(filepath.Dir(source))
	t.Logf("running test in workspace directory %s", workspace)
	bin := "phst_rules_elisp/tests/test_test"
	if runtime.GOOS == "windows" {
		bin += ".exe"
	}
	bin, err = runfiles.Path(bin)
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("using test binary %s", bin)
	runfilesEnv, err := runfiles.Env()
	if err != nil {
		t.Fatal(err)
	}
	tempDir := t.TempDir()
	reportName := filepath.Join(tempDir, "report.xml")
	coverageManifest := filepath.Join(tempDir, "coverage-manifest.txt")
	t.Logf("writing coverage manifest %s", coverageManifest)
	if err := os.WriteFile(coverageManifest, []byte("tests/test-lib.el\nunrelated.el\n"), 0400); err != nil {
		t.Error(err)
	}
	coverageDir := t.TempDir()

	cmd := exec.Command(bin, "arg 1", "arg\n2")
	// See
	// https://docs.bazel.build/versions/4.1.0/test-encyclopedia.html#initial-conditions.
	cmd.Env = append(os.Environ(), append(runfilesEnv,
		"XML_OUTPUT_FILE="+reportName,
		"TESTBRIDGE_TEST_ONLY=(not (tag skip))",
		"COVERAGE=1",
		"COVERAGE_MANIFEST="+coverageManifest,
		"COVERAGE_DIR="+coverageDir,
		// Don’t have the Python launcher change the current directory,
		// otherwise our fake environment setup won’t work.  See
		// https://github.com/bazelbuild/bazel/issues/7190.
		"RUN_UNDER_RUNFILES=0")...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = workspace
	switch err := cmd.Run().(type) {
	case nil:
		t.Error("test binary succeeded unexpectedly")
	case *exec.ExitError:
		if err.ExitCode() != 1 {
			t.Errorf("test binary: got exit code %d, want 1", err.ExitCode())
		}
	default:
		t.Error(err)
	}
	t.Log("test process exited")

	schema, err := runfiles.Path("junit_xsd/file/JUnit.xsd")
	if err != nil {
		t.Fatal(err)
	}
	t.Logf("validing XML report %s against schema %s", reportName, schema)
	cmd = exec.Command("xmllint", "--nonet", "--noout", "--schema", schema, reportName)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		t.Errorf("error validating XML report file: %s", err)
	}
	t.Log("XML validation complete")

	t.Log("parsing XML report")
	b, err := ioutil.ReadFile(reportName)
	if err != nil {
		t.Fatal(err)
	}
	type message struct {
		Message     string `xml:"message,attr"`
		Type        string `xml:"type,attr"`
		Description string `xml:",chardata"`
	}
	isDescription := func(p cmp.Path) bool {
		f, ok := p.Last().(cmp.StructField)
		return ok && f.Name() == "Description"
	}
	type property struct {
		Name  string `xml:"name,attr"`
		Value string `xml:"value,attr"`
	}
	type properties struct {
		Properties []property `xml:"property"`
	}
	type testCase struct {
		Name      string    `xml:"name,attr"`
		ClassName string    `xml:"classname,attr"`
		Time      float64   `xml:"time,attr"`
		Skipped   *struct{} `xml:"skipped"`
		Error     message   `xml:"error"`
		Failure   message   `xml:"failure"`
	}
	type report struct {
		XMLName    xml.Name
		Name       string     `xml:"name,attr"`
		Tests      int        `xml:"tests,attr"`
		Errors     int        `xml:"errors,attr"`
		Failures   int        `xml:"failures,attr"`
		Skipped    int        `xml:"skipped,attr"`
		Time       float64    `xml:"time,attr"`
		Timestamp  timestamp  `xml:"timestamp,attr"`
		Properties properties `xml:"properties"`
		TestCases  []testCase `xml:"testcase"`
	}
	var gotReport report
	if err := xml.Unmarshal(b, &gotReport); err != nil {
		t.Error(err)
	}
	var emacsVersion string
	for _, prop := range gotReport.Properties.Properties {
		if prop.Name == "emacs-version" {
			emacsVersion = prop.Value
			break
		}
	}
	if !regexp.MustCompile(`^\d+\.\d+`).MatchString(emacsVersion) {
		t.Errorf("invalid Emacs version %q", emacsVersion)
	}
	t.Logf("got Emacs version %s", emacsVersion)
	// Margin for time comparisons.  One hour is excessive, but we only
	// care about catching obvious bugs here.
	const margin = time.Hour
	// This, together with the EquateApprox below, ensures that the elapsed
	// time is nonnegative and below the margin.
	wantElapsed := margin.Seconds() / 2
	wantReport := report{
		XMLName:    xml.Name{"", "testsuite"},
		Name:       "ERT",
		Tests:      12,
		Errors:     0,
		Failures:   7,
		Skipped:    1,
		Time:       wantElapsed,
		Timestamp:  timestamp(time.Now()),
		Properties: properties{[]property{{"emacs-version", emacsVersion}}},
		TestCases: []testCase{
			{
				Name: "abort", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `peculiar error: "Boo"`, Type: `undefined-error-symbol`, Description: "something"},
			},
			{Name: "command-line", ClassName: "ERT", Time: wantElapsed},
			{Name: "coverage", ClassName: "ERT", Time: wantElapsed},
			{
				Name: "error", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `Boo`, Type: `error`, Description: "something"},
			},
			{
				Name: "ert-fail", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `Test failed: "Fail!"`, Type: `ert-test-failed`, Description: "something"},
			},
			{Name: "expect-failure", ClassName: "ERT", Time: wantElapsed},
			{
				Name: "expect-failure-but-pass", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `Test passed unexpectedly`, Type: `error`},
			},
			{
				Name: "fail", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `Test failed: ((should (= 0 1)) :form (= 0 1) :value nil)`, Type: `ert-test-failed`, Description: "something"},
			},
			{Name: "pass", ClassName: "ERT", Time: wantElapsed},
			{Name: "skip", ClassName: "ERT", Time: wantElapsed, Skipped: new(struct{})},
			{
				Name: "special-chars", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: "Error äöü \t \n \\u0000 \uFFFD \\uFFFE \\uFFFF 𝑨 <![CDATA[ ]]> & < > \" ' <!-- -->", Type: `error`, Description: "something"},
			},
			{
				Name: "throw", ClassName: "ERT", Time: wantElapsed,
				Failure: message{Message: `No catch for tag: unknown-tag, hi`, Type: `no-catch`, Description: "something"},
			},
		},
	}
	if diff := cmp.Diff(
		gotReport, wantReport,
		cmp.Transformer("time.Time", toTime), cmpopts.EquateApprox(0, wantElapsed), cmpopts.EquateApproxTime(margin),
		// We only check that the description isn’t absent or empty.
		cmp.FilterPath(isDescription, cmp.Comparer(bothEmpty)),
	); diff != "" {
		t.Error("XML test report (-got +want):\n", diff)
	}

	t.Logf("looking for coverage files in %s", coverageDir)
	files, err := filepath.Glob(filepath.Join(coverageDir, "*.dat"))
	if err != nil {
		t.Error(err)
	}
	if len(files) != 1 {
		t.Fatalf("got %d coverage files %v, want exactly one", len(files), files)
	}
	b, err = ioutil.ReadFile(files[0])
	if err != nil {
		t.Error(err)
	}
	gotCoverage := string(b)
	// See geninfo(1) for the coverage file format.  The function hit count
	// doesn’t work yet for nested functions.  The jump in the suffix index
	// for the nested functions is due to
	// https://debbugs.gnu.org/cgi/bugreport.cgi?bug=41988.
	wantCoverage := `SF:tests/test-lib.el
FN:27,tests/test-function
FN:39,foo@cl-flet@1
FN:41,foo@cl-flet@3
FNDA:1,tests/test-function
FNDA:0,foo@cl-flet@1
FNDA:0,foo@cl-flet@3
FNF:3
FNH:1
BRDA:30,0,0,0
BRDA:30,0,1,1
BRDA:34,0,0,1
BRDA:34,0,1,0
BRDA:44,0,0,0
BRDA:44,0,1,1
BRDA:44,0,2,0
BRDA:44,0,3,0
BRDA:46,0,0,0
BRDA:46,0,1,1
BRDA:46,0,2,1
BRDA:46,0,3,0
BRDA:46,1,0,1
BRDA:46,1,1,0
BRDA:48,0,0,-
BRDA:48,0,1,-
BRDA:49,0,0,1
BRDA:49,0,1,0
BRDA:53,0,0,0
BRDA:53,0,1,1
BRDA:53,0,2,0
BRDA:53,0,3,0
BRDA:54,0,0,3
BRDA:54,0,1,0
BRF:24
BRH:9
DA:30,1
DA:31,1
DA:32,0
DA:33,1
DA:34,1
DA:35,1
DA:36,1
DA:37,0
DA:39,1
DA:40,1
DA:41,1
DA:42,1
DA:44,1
DA:45,1
DA:46,1
DA:47,0
DA:48,0
DA:49,1
DA:50,1
DA:51,0
DA:52,1
DA:53,1
DA:54,3
DA:56,1
LH:19
LF:24
end_of_record
`
	if strings.HasPrefix(emacsVersion, "26.") {
		// No branch coverage under Emacs 26 expected.
		t.Log("not checking for branch coverage")
		wantCoverage = regexp.MustCompile(`(?m)^BR.+\n`).ReplaceAllLiteralString(wantCoverage, "")
	}
	// Depending on the exact runfiles layout, the test runner might have
	// printed different representations of test filenames.
	gotCoverage = regexp.MustCompile(`(?m)^(SF:).+[/\\](tests[/\\]test-lib\.el)$`).ReplaceAllString(gotCoverage, "$1$2")
	if diff := cmp.Diff(gotCoverage, wantCoverage); diff != "" {
		t.Error("coverage report (-got +want):\n", diff)
	}
}

type timestamp time.Time

func (t *timestamp) UnmarshalText(b []byte) error {
	// The XML report format doesn’t allow timezones in timestamps, so
	// time.Time.UnmarshalText doesn’t work.
	u, err := time.Parse("2006-01-02T15:04:05", string(b))
	*t = timestamp(u)
	return err
}

func toTime(t timestamp) time.Time { return time.Time(t) }

func bothEmpty(a, b string) bool { return (a == "") == (b == "") }
<|endoftext|>


<commit_before>package sanity

import (
	//"database/sql"

	"github.com/ovh/cds/engine/api/action"
	"github.com/ovh/cds/engine/api/database"
	"github.com/ovh/cds/engine/api/worker"
	"github.com/ovh/cds/engine/log"
	"github.com/ovh/cds/sdk"
)

// CheckActionRequirements checks:
// - requirements are compabtible with each other
// - action is actually buildable by present worker models
func checkActionRequirements(db database.Querier, proj string, pip string, actionID int64) ([]sdk.Warning, error) {
	warns := []sdk.Warning{}
	var modelReq, hostnameReq int
	var modelName string

	// Load all action requirements
	a, err := action.LoadActionByID(db, actionID)
	if err != nil {
		log.Warning("CheckActionRequirements> Cannot load action %d: %s\n", actionID, err)
		return nil, err
	}

	w, modelReq, modelName, err := checkMultipleWorkerModelWarning(db, proj, pip, a)
	if err != nil {
		return nil, err
	}
	warns = append(warns, w...)

	w, hostnameReq, err = checkMultipleHostnameWarning(db, proj, pip, a)
	if err != nil {
		return nil, err
	}
	warns = append(warns, w...)

	// Load registered worker model
	wms, err := worker.LoadWorkerModels(db)
	if err != nil {
		log.Warning("CheckActionRequirements> Cannot LoadWorkerModels")
		return nil, err
	}

	w, err = checkNoWorkerModelMatchRequirement(db, proj, pip, a, wms, modelReq, hostnameReq)
	if err != nil {
		return nil, err
	}
	warns = append(warns, w...)

	// if we get 1 Model requirement -> check binary requirement with model capabilities
	if modelReq == 1 {
		w, err = checkIncompatibleBinaryWithModelRequirement(db, proj, pip, a, wms, modelName)
		if err != nil {
			return nil, err
		}
		warns = append(warns, w...)
	}

	return warns, nil
}

func checkMultipleWorkerModelWarning(db database.Querier, proj string, pip string, a *sdk.Action) ([]sdk.Warning, int, string, error) {
	var warns []sdk.Warning
	var modelName string
	areqs := a.Requirements

	// If > 1 Model requirement -> WRONG
	var modelReq int
	for _, r := range areqs {
		if r.Type == sdk.ModelRequirement {
			modelReq++
			modelName = r.Value
		}
		if modelReq > 1 {
			modelName = r.Value
			w := sdk.Warning{
				Action: sdk.Action{
					ID: a.ID,
				},
				ID: MultipleWorkerModelWarning,
				MessageParam: map[string]string{
					"ActionName":   a.Name,
					"PipelineName": pip,
					"ProjectKey":   proj,
				},
			}
			warns = append(warns, w)
			break
		}
	}

	return warns, modelReq, modelName, nil
}

func checkMultipleHostnameWarning(db database.Querier, proj string, pip string, a *sdk.Action) ([]sdk.Warning, int, error) {
	var warns []sdk.Warning
	areqs := a.Requirements

	// If > 1 hostname requirement -> WRONG
	var hostnameReq int
	for _, r := range areqs {
		if r.Type == sdk.HostnameRequirement {
			hostnameReq++
		}
	}

	if hostnameReq > 1 {
		w := sdk.Warning{
			Action: sdk.Action{
				ID: a.ID,
			},
			ID: MultipleWorkerModelWarning,
			MessageParam: map[string]string{
				"ActionName":   a.Name,
				"PipelineName": pip,
				"ProjectKey":   proj,
			},
		}
		warns = append(warns, w)
	}

	return warns, hostnameReq, nil
}

func checkNoWorkerModelMatchRequirement(db database.Querier, proj string, pip string, a *sdk.Action, wms []sdk.Model, modelReq int, hostnameReq int) ([]sdk.Warning, error) {
	var warns []sdk.Warning
	areqs := a.Requirements

	// Check all binary requirement are present in at least one model
	validModel := false
	for _, wm := range wms {
		ok := true
		for _, ar := range areqs {
			if ar.Type != sdk.BinaryRequirement {
				continue
			}
			found := false
			for _, wr := range wm.Capabilities {
				if wr.Value == ar.Value {
					found = true
					break
				}
			}
			if !found {
				// Model doesn't have this requirement
				ok = false
				break
			}
		}
		if ok {
			validModel = true
		}
	}
	if !validModel && modelReq == 0 && hostnameReq == 0 {
		w := sdk.Warning{
			Action: sdk.Action{
				ID: a.ID,
			},
			ID: NoWorkerModelMatchRequirement,
			MessageParam: map[string]string{
				"ActionName":   a.Name,
				"PipelineName": pip,
				"ProjectKey":   proj,
			},
		}
		warns = append(warns, w)
	}
	return warns, nil
}

func checkIncompatibleBinaryWithModelRequirement(db database.Querier, proj string, pip string, a *sdk.Action, wms []sdk.Model, modelName string) ([]sdk.Warning, error) {
	var warns []sdk.Warning
	var m sdk.Model
	areqs := a.Requirements

	// find worker model
	for _, wm := range wms {
		if wm.Name == modelName {
			m = wm
			break
		}
	}

	if m.Name == "" { // uh not found
		log.Warning("checkIncompatibleBinaryWithModelRequirement> Model '%s' not found\n", modelName)
		return nil, sdk.ErrNoWorkerModel
	}

	// now for each binary requirement in areqs, check it is found in model capas
	for _, b := range areqs {
		if b.Type != sdk.BinaryRequirement {
			continue
		}

		found := false
		for _, c := range m.Capabilities {
			if c.Type != sdk.BinaryRequirement {
				continue
			}
			if c.Value == b.Value {
				found = true
				break
			}
		}
		if !found {
			w := sdk.Warning{
				Action: sdk.Action{
					ID: a.ID,
				},
				ID: IncompatibleBinaryAndModelRequirements,
				MessageParam: map[string]string{
					"ActionName":        a.Name,
					"PipelineName":      pip,
					"ProjectKey":        proj,
					"ModelName":         modelName,
					"BinaryRequirement": b.Value,
				},
			}
			warns = append(warns, w)
		}
	}

	return warns, nil
}
<commit_msg>fix (engine): avoid creation of NoWorkerModelMatch warning with empty requirement<commit_after>package sanity

import (
	//"database/sql"

	"github.com/ovh/cds/engine/api/action"
	"github.com/ovh/cds/engine/api/database"
	"github.com/ovh/cds/engine/api/worker"
	"github.com/ovh/cds/engine/log"
	"github.com/ovh/cds/sdk"
)

// CheckActionRequirements checks:
// - requirements are compabtible with each other
// - action is actually buildable by present worker models
func checkActionRequirements(db database.Querier, proj string, pip string, actionID int64) ([]sdk.Warning, error) {
	warns := []sdk.Warning{}
	var modelReq, hostnameReq int
	var modelName string

	// Load all action requirements
	a, err := action.LoadActionByID(db, actionID)
	if err != nil {
		log.Warning("CheckActionRequirements> Cannot load action %d: %s\n", actionID, err)
		return nil, err
	}

	w, modelReq, modelName, err := checkMultipleWorkerModelWarning(db, proj, pip, a)
	if err != nil {
		return nil, err
	}
	warns = append(warns, w...)

	w, hostnameReq, err = checkMultipleHostnameWarning(db, proj, pip, a)
	if err != nil {
		return nil, err
	}
	warns = append(warns, w...)

	// Load registered worker model
	wms, err := worker.LoadWorkerModels(db)
	if err != nil {
		log.Warning("CheckActionRequirements> Cannot LoadWorkerModels")
		return nil, err
	}

	w, err = checkNoWorkerModelMatchRequirement(db, proj, pip, a, wms, modelReq, hostnameReq)
	if err != nil {
		return nil, err
	}
	warns = append(warns, w...)

	// if we get 1 Model requirement -> check binary requirement with model capabilities
	if modelReq == 1 {
		w, err = checkIncompatibleBinaryWithModelRequirement(db, proj, pip, a, wms, modelName)
		if err != nil {
			return nil, err
		}
		warns = append(warns, w...)
	}

	return warns, nil
}

func checkMultipleWorkerModelWarning(db database.Querier, proj string, pip string, a *sdk.Action) ([]sdk.Warning, int, string, error) {
	var warns []sdk.Warning
	var modelName string
	areqs := a.Requirements

	// If > 1 Model requirement -> WRONG
	var modelReq int
	for _, r := range areqs {
		if r.Type == sdk.ModelRequirement {
			modelReq++
			modelName = r.Value
		}
		if modelReq > 1 {
			modelName = r.Value
			w := sdk.Warning{
				Action: sdk.Action{
					ID: a.ID,
				},
				ID: MultipleWorkerModelWarning,
				MessageParam: map[string]string{
					"ActionName":   a.Name,
					"PipelineName": pip,
					"ProjectKey":   proj,
				},
			}
			warns = append(warns, w)
			break
		}
	}

	return warns, modelReq, modelName, nil
}

func checkMultipleHostnameWarning(db database.Querier, proj string, pip string, a *sdk.Action) ([]sdk.Warning, int, error) {
	var warns []sdk.Warning
	areqs := a.Requirements

	// If > 1 hostname requirement -> WRONG
	var hostnameReq int
	for _, r := range areqs {
		if r.Type == sdk.HostnameRequirement {
			hostnameReq++
		}
	}

	if hostnameReq > 1 {
		w := sdk.Warning{
			Action: sdk.Action{
				ID: a.ID,
			},
			ID: MultipleWorkerModelWarning,
			MessageParam: map[string]string{
				"ActionName":   a.Name,
				"PipelineName": pip,
				"ProjectKey":   proj,
			},
		}
		warns = append(warns, w)
	}

	return warns, hostnameReq, nil
}

func checkNoWorkerModelMatchRequirement(db database.Querier, proj string, pip string, a *sdk.Action, wms []sdk.Model, modelReq int, hostnameReq int) ([]sdk.Warning, error) {
	var warns []sdk.Warning
	areqs := a.Requirements

	if len(a.Requirements) == 0 {
		return warns, nil
	}

	// Check all binary requirement are present in at least one model
	validModel := false
	for _, wm := range wms {
		ok := true
		for _, ar := range areqs {
			if ar.Type != sdk.BinaryRequirement {
				continue
			}
			found := false
			for _, wr := range wm.Capabilities {
				if wr.Value == ar.Value {
					found = true
					break
				}
			}
			if !found {
				// Model doesn't have this requirement
				ok = false
				break
			}
		}
		if ok {
			validModel = true
		}
	}
	if !validModel && modelReq == 0 && hostnameReq == 0 {
		w := sdk.Warning{
			Action: sdk.Action{
				ID: a.ID,
			},
			ID: NoWorkerModelMatchRequirement,
			MessageParam: map[string]string{
				"ActionName":   a.Name,
				"PipelineName": pip,
				"ProjectKey":   proj,
			},
		}
		warns = append(warns, w)
	}
	return warns, nil
}

func checkIncompatibleBinaryWithModelRequirement(db database.Querier, proj string, pip string, a *sdk.Action, wms []sdk.Model, modelName string) ([]sdk.Warning, error) {
	var warns []sdk.Warning
	var m sdk.Model
	areqs := a.Requirements

	// find worker model
	for _, wm := range wms {
		if wm.Name == modelName {
			m = wm
			break
		}
	}

	if m.Name == "" { // uh not found
		log.Warning("checkIncompatibleBinaryWithModelRequirement> Model '%s' not found\n", modelName)
		return nil, sdk.ErrNoWorkerModel
	}

	// now for each binary requirement in areqs, check it is found in model capas
	for _, b := range areqs {
		if b.Type != sdk.BinaryRequirement {
			continue
		}

		found := false
		for _, c := range m.Capabilities {
			if c.Type != sdk.BinaryRequirement {
				continue
			}
			if c.Value == b.Value {
				found = true
				break
			}
		}
		if !found {
			w := sdk.Warning{
				Action: sdk.Action{
					ID: a.ID,
				},
				ID: IncompatibleBinaryAndModelRequirements,
				MessageParam: map[string]string{
					"ActionName":        a.Name,
					"PipelineName":      pip,
					"ProjectKey":        proj,
					"ModelName":         modelName,
					"BinaryRequirement": b.Value,
				},
			}
			warns = append(warns, w)
		}
	}

	return warns, nil
}
<|endoftext|>


<commit_before>// Copyright 2014 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.

// This file defines types representing a selection in a text editor, i.e.,
// a range of text within a file.

package text

import (
	"fmt"
	"go/token"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

// A Selection represents a range of text within a particular file.  It is
// used to represent a selection in a text editor.
type Selection interface {
	// Convert returns start and end positions corresponding to this
	// selection.  It returns an error if this selection corresponds to a
	// file that is not in the given FileSet, or if the selected region is
	// not in range.
	Convert(*token.FileSet) (token.Pos, token.Pos, error)
	// GetFilename returns the file containing this selection.  The
	// returned filename may be an absolute or relative path and does is
	// not guaranteed to correspond to a valid file.
	GetFilename() string
	// String returns a human-readable representation of this Selection.
	String() string
}

// A LineColSelection is a Selection consisting of a filename, the line/column
// where the selected text begins, and the line/column where the text selection
// ends.  The end line and column must be greater than or equal to the start
// line and column, respectively.  Line and column numbers are 1-based.
type LineColSelection struct {
	Filename  string
	StartLine int
	StartCol  int
	EndLine   int
	EndCol    int
}

func (lc *LineColSelection) Convert(fset *token.FileSet) (token.Pos, token.Pos, error) {
	file := findFile(fset, lc.Filename)
	if file == nil {
		// error message from findQueryPos in go.tools/oracle/pos.go
		return 0, 0, fmt.Errorf("couldn't find file containing position")
	}

	startPos, err := lineColToPos(file, lc.StartLine, lc.StartCol)
	if err != nil {
		return 0, 0, err
	}

	endPos, err := lineColToPos(file, lc.EndLine, lc.EndCol)
	if err != nil {
		return 0, 0, err
	}
	return startPos, endPos, nil
}

func (lc *LineColSelection) GetFilename() string {
	return lc.Filename
}

func (lc *LineColSelection) String() string {
	return fmt.Sprintf("%s: %d,%d:%d,%d", lc.Filename,
		lc.StartLine, lc.StartCol, lc.EndLine, lc.EndCol)
}

// An OffsetLength selection is a selection that consists
// of a filename, an offset integer where the text selection
// begins, and a length integer of how long the selection is.
type OffsetLengthSelection struct {
	Filename string
	Offset   int
	Length   int
}

func (ol *OffsetLengthSelection) Convert(fset *token.FileSet) (token.Pos, token.Pos, error) {
	file := findFile(fset, ol.Filename)
	if file == nil {
		// error message from findQueryPos in go.tools/oracle/pos.go
		return 0, 0, fmt.Errorf("couldn't find file containing position")
	}
	offset := file.Pos(ol.Offset)
	length := file.Pos(ol.Length)
	return offset, length, nil
}

func (ol *OffsetLengthSelection) GetFilename() string {
	return ol.Filename
}

func (ol *OffsetLengthSelection) String() string {
	return fmt.Sprintf("%s: %d,%d", ol.Filename,
		ol.Offset, ol.Length)
}

// findFile returns the file corresponding to the given filename, or nil if no
// file can be found with that filename.  The absolute path of the returned
// file can be found via f.Name().
func findFile(fset *token.FileSet, filename string) *token.File {
	// from findQueryPos in go.tools/oracle/pos.go
	var file *token.File
	fset.Iterate(func(f *token.File) bool {
		if sameFile(filename, f.Name()) {
			file = f
			return false // done
		}
		return true // continue
	})
	return file
}

// sameFile returns true if x and y have the same basename and denote
// the same file.
func sameFile(x, y string) bool { // from go.tools/oracle/pos.go
	if filepath.Base(x) == filepath.Base(y) { // (optimisation)
		if xi, err := os.Stat(x); err == nil {
			if yi, err := os.Stat(y); err == nil {
				return os.SameFile(xi, yi)
			}
		}
	}
	return false
}

// lineColToPos converts a line/column position to a token.Pos.  The first
// character in a file is considered to be at line 1, column 1.
func lineColToPos(file *token.File, line int, column int) (token.Pos, error) {
	// Binary search to find a position on the given line
	lastOffset := file.Size() - 1
	start := 0
	end := lastOffset
	mid := (start + end) / 2
	for start <= end {
		midLine := file.Line(file.Pos(mid))
		if line == midLine {
			break
		} else if line < midLine {
			end = mid - 1
		} else /* line > midLine */ {
			start = mid + 1
		}
		mid = (start + end) / 2
	}

	// Now mid is some position on the correct line; add/subtract to find
	// the position at the correct column
	difference := file.Position(file.Pos(mid)).Column - column
	pos := file.Pos(mid - difference)
	p := file.Position(pos)
	if p.Line != line || p.Column != column {
		return pos, fmt.Errorf("Invalid position: line %d, column %d",
			line, column)
	}
	return pos, nil
}

// TODO add piece that conditionally checks if offset/length or row/col
// Returns a new Selection type that will either be LineColSelection
// or OffsetLengthSelection
func NewSelection(filename string, pos string) (Selection, error) {
	absFilename, err := filepath.Abs(filename)
	if err != nil {
		return nil, fmt.Errorf("invalid filename")
	}

	args := strings.Split(pos, ":")

	if len(args) < 2 {
		return nil, fmt.Errorf("invalid -pos")
	}

	sl, sc := parseLineCol(args[0])
	el, ec := parseLineCol(args[1])

	if sl < 0 || sc < 0 || el < 0 || ec < 0 {
		return nil, fmt.Errorf("invalid -pos line, col")
	}

	return &LineColSelection{Filename: absFilename, StartLine: sl, StartCol: sc,
		EndLine: el, EndCol: ec}, nil
}

// e.g. 302,6
func parseLineCol(linecol string) (int, int) {
	lc := strings.Split(linecol, ",")
	if l, err := strconv.ParseInt(lc[0], 10, 32); err == nil {
		if c, err := strconv.ParseInt(lc[1], 10, 32); err == nil {
			return int(l), int(c)
		}
	}

	return -1, -1
}
<commit_msg>added conditional checking in NewSelection<commit_after>// Copyright 2014 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.

// This file defines types representing a selection in a text editor, i.e.,
// a range of text within a file.

package text

import (
	"fmt"
	"go/token"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

// A Selection represents a range of text within a particular file.  It is
// used to represent a selection in a text editor.
type Selection interface {
	// Convert returns start and end positions corresponding to this
	// selection.  It returns an error if this selection corresponds to a
	// file that is not in the given FileSet, or if the selected region is
	// not in range.
	Convert(*token.FileSet) (token.Pos, token.Pos, error)
	// GetFilename returns the file containing this selection.  The
	// returned filename may be an absolute or relative path and does is
	// not guaranteed to correspond to a valid file.
	GetFilename() string
	// String returns a human-readable representation of this Selection.
	String() string
}

// A LineColSelection is a Selection consisting of a filename, the line/column
// where the selected text begins, and the line/column where the text selection
// ends.  The end line and column must be greater than or equal to the start
// line and column, respectively.  Line and column numbers are 1-based.
type LineColSelection struct {
	Filename  string
	StartLine int
	StartCol  int
	EndLine   int
	EndCol    int
}

func (lc *LineColSelection) Convert(fset *token.FileSet) (token.Pos, token.Pos, error) {
	file := findFile(fset, lc.Filename)
	if file == nil {
		// error message from findQueryPos in go.tools/oracle/pos.go
		return 0, 0, fmt.Errorf("couldn't find file containing position")
	}

	startPos, err := lineColToPos(file, lc.StartLine, lc.StartCol)
	if err != nil {
		return 0, 0, err
	}

	endPos, err := lineColToPos(file, lc.EndLine, lc.EndCol)
	if err != nil {
		return 0, 0, err
	}
	return startPos, endPos, nil
}

func (lc *LineColSelection) GetFilename() string {
	return lc.Filename
}

func (lc *LineColSelection) String() string {
	return fmt.Sprintf("%s: %d,%d:%d,%d", lc.Filename,
		lc.StartLine, lc.StartCol, lc.EndLine, lc.EndCol)
}

// An OffsetLength selection is a selection that consists
// of a filename, an offset integer where the text selection
// begins, and a length integer of how long the selection is.
type OffsetLengthSelection struct {
	Filename string
	Offset   int
	Length   int
}

func (ol *OffsetLengthSelection) Convert(fset *token.FileSet) (token.Pos, token.Pos, error) {
	file := findFile(fset, ol.Filename)
	if file == nil {
		// error message from findQueryPos in go.tools/oracle/pos.go
		return 0, 0, fmt.Errorf("couldn't find file containing position")
	}
	offset := file.Pos(ol.Offset)
	length := file.Pos(ol.Length)
	return offset, length, nil
}

func (ol *OffsetLengthSelection) GetFilename() string {
	return ol.Filename
}

func (ol *OffsetLengthSelection) String() string {
	return fmt.Sprintf("%s: %d,%d", ol.Filename,
		ol.Offset, ol.Length)
}

// findFile returns the file corresponding to the given filename, or nil if no
// file can be found with that filename.  The absolute path of the returned
// file can be found via f.Name().
func findFile(fset *token.FileSet, filename string) *token.File {
	// from findQueryPos in go.tools/oracle/pos.go
	var file *token.File
	fset.Iterate(func(f *token.File) bool {
		if sameFile(filename, f.Name()) {
			file = f
			return false // done
		}
		return true // continue
	})
	return file
}

// sameFile returns true if x and y have the same basename and denote
// the same file.
func sameFile(x, y string) bool { // from go.tools/oracle/pos.go
	if filepath.Base(x) == filepath.Base(y) { // (optimisation)
		if xi, err := os.Stat(x); err == nil {
			if yi, err := os.Stat(y); err == nil {
				return os.SameFile(xi, yi)
			}
		}
	}
	return false
}

// lineColToPos converts a line/column position to a token.Pos.  The first
// character in a file is considered to be at line 1, column 1.
func lineColToPos(file *token.File, line int, column int) (token.Pos, error) {
	// Binary search to find a position on the given line
	lastOffset := file.Size() - 1
	start := 0
	end := lastOffset
	mid := (start + end) / 2
	for start <= end {
		midLine := file.Line(file.Pos(mid))
		if line == midLine {
			break
		} else if line < midLine {
			end = mid - 1
		} else /* line > midLine */ {
			start = mid + 1
		}
		mid = (start + end) / 2
	}

	// Now mid is some position on the correct line; add/subtract to find
	// the position at the correct column
	difference := file.Position(file.Pos(mid)).Column - column
	pos := file.Pos(mid - difference)
	p := file.Position(pos)
	if p.Line != line || p.Column != column {
		return pos, fmt.Errorf("Invalid position: line %d, column %d",
			line, column)
	}
	return pos, nil
}

// TODO add piece that conditionally checks if offset/length or row/col
// Returns a new Selection type that will either be LineColSelection
// or OffsetLengthSelection
func NewSelection(filename string, pos string) (Selection, error) {
	absFilename, err := filepath.Abs(filename)
	if err != nil {
		return nil, fmt.Errorf("invalid filename")
	}

	if strings.Contains(pos, ":") {
		args := strings.Split(pos, ":")

		if len(args) < 2 {
			return nil, fmt.Errorf("invalid -pos")
		}

		sl, sc := parseLineCol(args[0])
		el, ec := parseLineCol(args[1])

		if sl < 0 || sc < 0 || el < 0 || ec < 0 {
			return nil, fmt.Errorf("invalid -pos line, col")
		}

		return &LineColSelection{Filename: absFilename, StartLine: sl, StartCol: sc,
			EndLine: el, EndCol: ec}, nil
	} else {
		offset, length := parseLineCol(pos)
		if offset < 0 || length < 0 || length < offset {
			return nil, fmt.Errorf("invalid -pos offset, length")
		}

		return &OffsetLengthSelection{Filename: absFilename, Offset: offset, Length: length}, nil
	}
}

// e.g. 302,6
func parseLineCol(linecol string) (int, int) {
	lc := strings.Split(linecol, ",")
	if l, err := strconv.ParseInt(lc[0], 10, 32); err == nil {
		if c, err := strconv.ParseInt(lc[1], 10, 32); err == nil {
			return int(l), int(c)
		}
	}

	return -1, -1
}
<|endoftext|>