<commit_before>package main

import (
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"sort"
	"testing"

	"github.com/Songmu/gitconfig"
)

func samePathSlice(lhss, rhss []string) bool {
	sort.Strings(lhss)
	sort.Strings(rhss)
	for i := range lhss {
		if !samePath(lhss[i], rhss[i]) {
			return false
		}
	}
	return true
}

func TestLocalRepositoryFromFullPath(t *testing.T) {
	defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
	tmproot := newTempDir(t)
	defer os.RemoveAll(tmproot)
	_localRepositoryRoots = []string{tmproot}

	testCases := []struct {
		fpath    string
		expect   string
		subpaths []string
	}{{
		fpath:    filepath.Join(tmproot, "github.com/motemen/ghq"),
		expect:   "motemen/ghq",
		subpaths: []string{"ghq", "motemen/ghq", "github.com/motemen/ghq"},
	}, {
		fpath:    filepath.Join(tmproot, "stash.com/scm/motemen/ghq"),
		expect:   "scm/motemen/ghq",
		subpaths: []string{"ghq", "motemen/ghq", "scm/motemen/ghq", "stash.com/scm/motemen/ghq"},
	}}

	for _, tc := range testCases {
		t.Run(tc.fpath, func(t *testing.T) {
			r, err := LocalRepositoryFromFullPath(tc.fpath, nil)
			if err != nil {
				t.Errorf("error should be nil but: %s", err)
				return
			}
			if r.NonHostPath() != tc.expect {
				t.Errorf("NonHostPath: got: %s, expect: %s", r.NonHostPath(), tc.expect)
			}
			if !reflect.DeepEqual(r.Subpaths(), tc.subpaths) {
				t.Errorf("Subpaths:\ngot:    %+v\nexpect: %+v", r.Subpaths(), tc.subpaths)
			}
		})
	}
}

func TestNewLocalRepository(t *testing.T) {
	defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
	tmproot := newTempDir(t)
	defer os.RemoveAll(tmproot)
	_localRepositoryRoots = []string{tmproot}

	testCases := []struct {
		name, url, expect string
	}{{
		name:   "GitHub",
		url:    "ssh://git@github.com/motemen/ghq.git",
		expect: filepath.Join(tmproot, "github.com/motemen/ghq"),
	}, {
		name:   "stash",
		url:    "ssh://git@stash.com/scm/motemen/ghq.git",
		expect: filepath.Join(tmproot, "stash.com/scm/motemen/ghq"),
	}, {
		name:   "svn Sourceforge",
		url:    "http://svn.code.sf.net/p/ghq/code/trunk",
		expect: filepath.Join(tmproot, "svn.code.sf.net/p/ghq/code/trunk"),
	}, {
		name:   "git Sourceforge",
		url:    "http://git.code.sf.net/p/ghq/code",
		expect: filepath.Join(tmproot, "git.code.sf.net/p/ghq/code"),
	}, {
		name:   "svn Sourceforge JP",
		url:    "http://scm.sourceforge.jp/svnroot/ghq/",
		expect: filepath.Join(tmproot, "scm.sourceforge.jp/svnroot/ghq"),
	}, {
		name:   "git Sourceforge JP",
		url:    "http://scm.sourceforge.jp/gitroot/ghq/ghq.git",
		expect: filepath.Join(tmproot, "scm.sourceforge.jp/gitroot/ghq/ghq"),
	}, {
		name:   "svn Assembla",
		url:    "https://subversion.assembla.com/svn/ghq/",
		expect: filepath.Join(tmproot, "subversion.assembla.com/svn/ghq"),
	}, {
		name:   "git Assembla",
		url:    "https://git.assembla.com/ghq.git",
		expect: filepath.Join(tmproot, "git.assembla.com/ghq"),
	}, {
		name:   "bitbucket host with port",
		url:    "https://bitbucket.local:8888/motemen/ghq.git",
		expect: filepath.Join(tmproot, "bitbucket.local/motemen/ghq"),
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			defer func(orig string) { _home = orig }(_home)
			_home = ""
			defer gitconfig.WithConfig(t, "")()
			r, err := LocalRepositoryFromURL(mustParseURL(tc.url))
			if err != nil {
				t.Errorf("error should be nil but: %s", err)
			}
			if r.FullPath != tc.expect {
				t.Errorf("got: %s, expect: %s", r.FullPath, tc.expect)
			}
		})
	}
}

func TestLocalRepositoryRoots(t *testing.T) {
	defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
	defer func(orig string) { os.Setenv("GHQ_ROOT", orig) }(os.Getenv("GHQ_ROOT"))

	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}

	testCases := []struct {
		root   string
		expect []string
	}{{
		root:   "/path/to/ghqroot",
		expect: []string{"/path/to/ghqroot"},
	}, {
		root:   "/path/to/ghqroot1" + string(os.PathListSeparator) + "/path/to/ghqroot2",
		expect: []string{"/path/to/ghqroot1", "/path/to/ghqroot2"},
	}, {
		root:   "/path/to/ghqroot11" + string(os.PathListSeparator) + "vendor",
		expect: []string{"/path/to/ghqroot11", filepath.Join(wd, "vendor")},
	}}

	for _, tc := range testCases {
		t.Run(tc.root, func(t *testing.T) {
			_localRepositoryRoots = nil
			os.Setenv("GHQ_ROOT", tc.root)
			got, err := localRepositoryRoots(true)
			if err != nil {
				t.Errorf("error should be nil, but: %s", err)
			}
			if !samePathSlice(got, tc.expect) {
				t.Errorf("\ngot:    %+v\nexpect: %+v", got, tc.expect)
			}
		})
	}
}

// https://gist.github.com/kyanny/c231f48e5d08b98ff2c3
func TestList_Symlink(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.SkipNow()
	}
	root := newTempDir(t)
	defer os.RemoveAll(root)

	symDir := newTempDir(t)
	defer os.RemoveAll(symDir)

	origLocalRepositryRoots := _localRepositoryRoots
	_localRepositoryRoots = []string{root}
	defer func() { _localRepositoryRoots = origLocalRepositryRoots }()

	if err := os.MkdirAll(filepath.Join(root, "github.com", "atom", "atom", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.MkdirAll(filepath.Join(root, "github.com", "zabbix", "zabbix", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.Symlink(symDir, filepath.Join(root, "github.com", "ghq")); err != nil {
		t.Fatal(err)
	}

	paths := []string{}
	walkAllLocalRepositories(func(repo *LocalRepository) {
		paths = append(paths, repo.RelPath)
	})

	if len(paths) != 2 {
		t.Errorf("length of paths should be 2, but: %d", len(paths))
	}
}

func TestList_Symlink_In_Same_Directory(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.SkipNow()
	}
	root := newTempDir(t)
	defer os.RemoveAll(root)

	symDir := newTempDir(t)
	defer os.RemoveAll(symDir)

	origLocalRepositryRoots := _localRepositoryRoots
	_localRepositoryRoots = []string{root}
	defer func() { _localRepositoryRoots = origLocalRepositryRoots }()

	if err := os.MkdirAll(filepath.Join(root, "github.com", "root-user", "a-repository", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.MkdirAll(filepath.Join(root, "github.com", "root-user", "z-repository", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.MkdirAll(filepath.Join(symDir, "github.com", "sym-user", "h-repository", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.Symlink(filepath.Join(symDir, "github.com", "sym-user", "h-repository"), filepath.Join(root, "github.com", "root-user", "h-repository")); err != nil {
		t.Fatal(err)
	}

	paths := []string{}
	walkAllLocalRepositories(func(repo *LocalRepository) {
		paths = append(paths, repo.RelPath)
	})

	if len(paths) != 3 {
		t.Errorf("length of paths should be 3, but: %d", len(paths))
	}
}

func TestFindVCSBackend(t *testing.T) {
	testCases := []struct {
		name   string
		setup  func(t *testing.T) (string, string, func())
		expect *VCSBackend
	}{{
		name: "git",
		setup: func(t *testing.T) (string, string, func()) {
			dir := newTempDir(t)
			os.MkdirAll(filepath.Join(dir, ".git"), 0755)
			return dir, "", func() {
				os.RemoveAll(dir)
			}
		},
		expect: GitBackend,
	}, {
		name: "git svn",
		setup: func(t *testing.T) (string, string, func()) {
			dir := newTempDir(t)
			os.MkdirAll(filepath.Join(dir, ".git", "svn"), 0755)
			return dir, "", func() {
				os.RemoveAll(dir)
			}
		},
		expect: GitBackend,
	}, {
		name: "git with matched vcs",
		setup: func(t *testing.T) (string, string, func()) {
			dir := newTempDir(t)
			os.MkdirAll(filepath.Join(dir, ".git"), 0755)
			return dir, "git", func() {
				os.RemoveAll(dir)
			}
		},
		expect: GitBackend,
	}, {
		name: "git with not matched vcs",
		setup: func(t *testing.T) (string, string, func()) {
			dir := newTempDir(t)
			os.MkdirAll(filepath.Join(dir, ".git"), 0755)
			return dir, "mercurial", func() {
				os.RemoveAll(dir)
			}
		},
		expect: nil,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			fpath, vcs, teardown := tc.setup(t)
			defer teardown()
			backend := findVCSBackend(fpath, vcs)
			if backend != tc.expect {
				t.Errorf("got: %v, expect: %v", backend, tc.expect)
			}
		})
	}
}

func TestLocalRepository_VCS(t *testing.T) {
	defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
	defer func(orig string) { os.Setenv("GHQ_ROOT", orig) }(os.Getenv("GHQ_ROOT"))

	_localRepositoryRoots = nil
	tmpdir := newTempDir(t)
	os.Setenv("GHQ_ROOT", tmpdir)

	pkg := filepath.Join(tmpdir, "github.com", "motemen", "ghq")
	subpkg := filepath.Join(pkg, "logger")

	os.MkdirAll(filepath.Join(pkg, ".git"), 0755)
	os.MkdirAll(subpkg, 0755)

	t.Run("reporoot", func(t *testing.T) {
		repo, err := LocalRepositoryFromFullPath(pkg, nil)
		if err != nil {
			t.Errorf("error should be nil, but: %s (%s)", err, pkg)
			return
		}
		vcs, repoPath := repo.VCS()
		if vcs != GitBackend {
			t.Errorf("repo.VCS() = %+v, expect: GitBackend", vcs)
			return
		}
		if repoPath != pkg {
			t.Errorf("got: %s, expect: %s", repoPath, pkg)
		}
	})

	t.Run("subdir", func(t *testing.T) {
		repo, err := LocalRepositoryFromFullPath(subpkg, nil)
		if err != nil {
			t.Errorf("error should be nil, but: %s", err)
			return
		}
		vcs, repoPath := repo.VCS()
		if vcs != GitBackend {
			t.Errorf("repo.VCS() = %+v, expect: GitBackend", vcs)
		}
		if repoPath != pkg {
			t.Errorf("got: %s, expect: %s", repoPath, pkg)
		}
	})
}

func TestURLMatchLocalRepositoryRoots(t *testing.T) {
	defer tmpEnv("HOME", "/home/tmp")()
	defer tmpEnv("USERPROFILE", `C:\home\tmp`)()
	defer func(orig string) { _home = orig }(_home)
	_home = ""
	defer gitconfig.WithConfig(t, `
[ghq]
  root = /hoge
[ghq "https://github.com/hatena"]
  root = ~/proj/hatena
  root = /backups/hatena
[ghq "https://github.com/natureglobal"]
  root = ~/proj/natureglobal
`)()

	want := []string{"/home/tmp/proj/hatena", "/backups/hatena", "/home/tmp/proj/natureglobal"}
	if runtime.GOOS == "windows" {
		want = []string{`C:\home\tmp\proj\hatena`, "/backups/hatena", `C:\home\tmp\proj\natureglobal`}
	}
	got, err := urlMatchLocalRepositoryRoots()
	if err != nil {
		t.Errorf("error should be nil but: %s", err)
	}
	if !reflect.DeepEqual(want, got) {
		t.Errorf("urlMatchLocalRepositoryRoots() = %+v, want: %+v", got, want)
	}
}
<commit_msg>skip test<commit_after>package main

import (
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"sort"
	"testing"

	"github.com/Songmu/gitconfig"
)

func samePathSlice(lhss, rhss []string) bool {
	sort.Strings(lhss)
	sort.Strings(rhss)
	for i := range lhss {
		if !samePath(lhss[i], rhss[i]) {
			return false
		}
	}
	return true
}

func TestLocalRepositoryFromFullPath(t *testing.T) {
	defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
	tmproot := newTempDir(t)
	defer os.RemoveAll(tmproot)
	_localRepositoryRoots = []string{tmproot}

	testCases := []struct {
		fpath    string
		expect   string
		subpaths []string
	}{{
		fpath:    filepath.Join(tmproot, "github.com/motemen/ghq"),
		expect:   "motemen/ghq",
		subpaths: []string{"ghq", "motemen/ghq", "github.com/motemen/ghq"},
	}, {
		fpath:    filepath.Join(tmproot, "stash.com/scm/motemen/ghq"),
		expect:   "scm/motemen/ghq",
		subpaths: []string{"ghq", "motemen/ghq", "scm/motemen/ghq", "stash.com/scm/motemen/ghq"},
	}}

	for _, tc := range testCases {
		t.Run(tc.fpath, func(t *testing.T) {
			r, err := LocalRepositoryFromFullPath(tc.fpath, nil)
			if err != nil {
				t.Errorf("error should be nil but: %s", err)
				return
			}
			if r.NonHostPath() != tc.expect {
				t.Errorf("NonHostPath: got: %s, expect: %s", r.NonHostPath(), tc.expect)
			}
			if !reflect.DeepEqual(r.Subpaths(), tc.subpaths) {
				t.Errorf("Subpaths:\ngot:    %+v\nexpect: %+v", r.Subpaths(), tc.subpaths)
			}
		})
	}
}

func TestNewLocalRepository(t *testing.T) {
	defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
	tmproot := newTempDir(t)
	defer os.RemoveAll(tmproot)
	_localRepositoryRoots = []string{tmproot}

	testCases := []struct {
		name, url, expect string
	}{{
		name:   "GitHub",
		url:    "ssh://git@github.com/motemen/ghq.git",
		expect: filepath.Join(tmproot, "github.com/motemen/ghq"),
	}, {
		name:   "stash",
		url:    "ssh://git@stash.com/scm/motemen/ghq.git",
		expect: filepath.Join(tmproot, "stash.com/scm/motemen/ghq"),
	}, {
		name:   "svn Sourceforge",
		url:    "http://svn.code.sf.net/p/ghq/code/trunk",
		expect: filepath.Join(tmproot, "svn.code.sf.net/p/ghq/code/trunk"),
	}, {
		name:   "git Sourceforge",
		url:    "http://git.code.sf.net/p/ghq/code",
		expect: filepath.Join(tmproot, "git.code.sf.net/p/ghq/code"),
	}, {
		name:   "svn Sourceforge JP",
		url:    "http://scm.sourceforge.jp/svnroot/ghq/",
		expect: filepath.Join(tmproot, "scm.sourceforge.jp/svnroot/ghq"),
	}, {
		name:   "git Sourceforge JP",
		url:    "http://scm.sourceforge.jp/gitroot/ghq/ghq.git",
		expect: filepath.Join(tmproot, "scm.sourceforge.jp/gitroot/ghq/ghq"),
	}, {
		name:   "svn Assembla",
		url:    "https://subversion.assembla.com/svn/ghq/",
		expect: filepath.Join(tmproot, "subversion.assembla.com/svn/ghq"),
	}, {
		name:   "git Assembla",
		url:    "https://git.assembla.com/ghq.git",
		expect: filepath.Join(tmproot, "git.assembla.com/ghq"),
	}, {
		name:   "bitbucket host with port",
		url:    "https://bitbucket.local:8888/motemen/ghq.git",
		expect: filepath.Join(tmproot, "bitbucket.local/motemen/ghq"),
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			defer func(orig string) { _home = orig }(_home)
			_home = ""
			defer gitconfig.WithConfig(t, "")()
			r, err := LocalRepositoryFromURL(mustParseURL(tc.url))
			if err != nil {
				t.Errorf("error should be nil but: %s", err)
			}
			if r.FullPath != tc.expect {
				t.Errorf("got: %s, expect: %s", r.FullPath, tc.expect)
			}
		})
	}
}

func TestLocalRepositoryRoots(t *testing.T) {
	defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
	defer func(orig string) { os.Setenv("GHQ_ROOT", orig) }(os.Getenv("GHQ_ROOT"))

	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}

	testCases := []struct {
		root   string
		expect []string
	}{{
		root:   "/path/to/ghqroot",
		expect: []string{"/path/to/ghqroot"},
	}, {
		root:   "/path/to/ghqroot1" + string(os.PathListSeparator) + "/path/to/ghqroot2",
		expect: []string{"/path/to/ghqroot1", "/path/to/ghqroot2"},
	}, {
		root:   "/path/to/ghqroot11" + string(os.PathListSeparator) + "vendor",
		expect: []string{"/path/to/ghqroot11", filepath.Join(wd, "vendor")},
	}}

	for _, tc := range testCases {
		t.Run(tc.root, func(t *testing.T) {
			_localRepositoryRoots = nil
			os.Setenv("GHQ_ROOT", tc.root)
			got, err := localRepositoryRoots(true)
			if err != nil {
				t.Errorf("error should be nil, but: %s", err)
			}
			if !samePathSlice(got, tc.expect) {
				t.Errorf("\ngot:    %+v\nexpect: %+v", got, tc.expect)
			}
		})
	}
}

// https://gist.github.com/kyanny/c231f48e5d08b98ff2c3
func TestList_Symlink(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.SkipNow()
	}
	root := newTempDir(t)
	defer os.RemoveAll(root)

	symDir := newTempDir(t)
	defer os.RemoveAll(symDir)

	origLocalRepositryRoots := _localRepositoryRoots
	_localRepositoryRoots = []string{root}
	defer func() { _localRepositoryRoots = origLocalRepositryRoots }()

	if err := os.MkdirAll(filepath.Join(root, "github.com", "atom", "atom", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.MkdirAll(filepath.Join(root, "github.com", "zabbix", "zabbix", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.Symlink(symDir, filepath.Join(root, "github.com", "ghq")); err != nil {
		t.Fatal(err)
	}

	paths := []string{}
	walkAllLocalRepositories(func(repo *LocalRepository) {
		paths = append(paths, repo.RelPath)
	})

	if len(paths) != 2 {
		t.Errorf("length of paths should be 2, but: %d", len(paths))
	}
}

func TestList_Symlink_In_Same_Directory(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.SkipNow()
	}
	root := newTempDir(t)
	defer os.RemoveAll(root)

	symDir := newTempDir(t)
	defer os.RemoveAll(symDir)

	origLocalRepositryRoots := _localRepositoryRoots
	_localRepositoryRoots = []string{root}
	defer func() { _localRepositoryRoots = origLocalRepositryRoots }()

	if err := os.MkdirAll(filepath.Join(root, "github.com", "root-user", "a-repository", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.MkdirAll(filepath.Join(root, "github.com", "root-user", "z-repository", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.MkdirAll(filepath.Join(symDir, "github.com", "sym-user", "h-repository", ".git"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.Symlink(filepath.Join(symDir, "github.com", "sym-user", "h-repository"), filepath.Join(root, "github.com", "root-user", "h-repository")); err != nil {
		t.Fatal(err)
	}

	paths := []string{}
	walkAllLocalRepositories(func(repo *LocalRepository) {
		paths = append(paths, repo.RelPath)
	})

	if len(paths) != 3 {
		t.Errorf("length of paths should be 3, but: %d", len(paths))
	}
}

func TestFindVCSBackend(t *testing.T) {
	testCases := []struct {
		name   string
		setup  func(t *testing.T) (string, string, func())
		expect *VCSBackend
	}{{
		name: "git",
		setup: func(t *testing.T) (string, string, func()) {
			dir := newTempDir(t)
			os.MkdirAll(filepath.Join(dir, ".git"), 0755)
			return dir, "", func() {
				os.RemoveAll(dir)
			}
		},
		expect: GitBackend,
	}, {
		name: "git svn",
		setup: func(t *testing.T) (string, string, func()) {
			dir := newTempDir(t)
			os.MkdirAll(filepath.Join(dir, ".git", "svn"), 0755)
			return dir, "", func() {
				os.RemoveAll(dir)
			}
		},
		expect: GitBackend,
	}, {
		name: "git with matched vcs",
		setup: func(t *testing.T) (string, string, func()) {
			dir := newTempDir(t)
			os.MkdirAll(filepath.Join(dir, ".git"), 0755)
			return dir, "git", func() {
				os.RemoveAll(dir)
			}
		},
		expect: GitBackend,
	}, {
		name: "git with not matched vcs",
		setup: func(t *testing.T) (string, string, func()) {
			dir := newTempDir(t)
			os.MkdirAll(filepath.Join(dir, ".git"), 0755)
			return dir, "mercurial", func() {
				os.RemoveAll(dir)
			}
		},
		expect: nil,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			fpath, vcs, teardown := tc.setup(t)
			defer teardown()
			backend := findVCSBackend(fpath, vcs)
			if backend != tc.expect {
				t.Errorf("got: %v, expect: %v", backend, tc.expect)
			}
		})
	}
}

func TestLocalRepository_VCS(t *testing.T) {
	defer func(orig []string) { _localRepositoryRoots = orig }(_localRepositoryRoots)
	defer func(orig string) { os.Setenv("GHQ_ROOT", orig) }(os.Getenv("GHQ_ROOT"))

	_localRepositoryRoots = nil
	tmpdir := newTempDir(t)
	os.Setenv("GHQ_ROOT", tmpdir)

	pkg := filepath.Join(tmpdir, "github.com", "motemen", "ghq")
	subpkg := filepath.Join(pkg, "logger")

	os.MkdirAll(filepath.Join(pkg, ".git"), 0755)
	os.MkdirAll(subpkg, 0755)

	t.Run("reporoot", func(t *testing.T) {
		repo, err := LocalRepositoryFromFullPath(pkg, nil)
		if err != nil {
			t.Errorf("error should be nil, but: %s (%s)", err, pkg)
			return
		}
		vcs, repoPath := repo.VCS()
		if vcs != GitBackend {
			t.Errorf("repo.VCS() = %+v, expect: GitBackend", vcs)
			return
		}
		if repoPath != pkg {
			t.Errorf("got: %s, expect: %s", repoPath, pkg)
		}
	})

	t.Run("subdir", func(t *testing.T) {
		repo, err := LocalRepositoryFromFullPath(subpkg, nil)
		if err != nil {
			t.Errorf("error should be nil, but: %s", err)
			return
		}
		vcs, repoPath := repo.VCS()
		if vcs != GitBackend {
			t.Errorf("repo.VCS() = %+v, expect: GitBackend", vcs)
		}
		if repoPath != pkg {
			t.Errorf("got: %s, expect: %s", repoPath, pkg)
		}
	})
}

func TestURLMatchLocalRepositoryRoots(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.SkipNow()
	}
	defer tmpEnv("HOME", "/home/tmp")()
	defer func(orig string) { _home = orig }(_home)
	_home = ""
	defer gitconfig.WithConfig(t, `
[ghq]
  root = /hoge
[ghq "https://github.com/hatena"]
  root = ~/proj/hatena
  root = /backups/hatena
[ghq "https://github.com/natureglobal"]
  root = ~/proj/natureglobal
`)()

	want := []string{"/home/tmp/proj/hatena", "/backups/hatena", "/home/tmp/proj/natureglobal"}
	got, err := urlMatchLocalRepositoryRoots()
	if err != nil {
		t.Errorf("error should be nil but: %s", err)
	}
	if !reflect.DeepEqual(want, got) {
		t.Errorf("urlMatchLocalRepositoryRoots() = %+v, want: %+v", got, want)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"airdispat.ch/identity"
	"airdispat.ch/message"
	"airdispat.ch/server"
	"database/sql"
	"errors"
	"flag"
	"fmt"
	"github.com/coopernurse/gorp"
	_ "github.com/lib/pq"
	"melange/app/models"
	"melange/mailserver"
	"net"
	"os"
)

var port = flag.String("port", "2048", "select the port on which to run the mail server")
var me = flag.String("me", getServerLocation(), "the location of the server that should be broadcast")

var key_file = flag.String("key", "", "the file to store keys")

var db_conn = flag.String("db", "", "the connection string for db.Open")

func getServerLocation() string {
	s, _ := os.Hostname()
	ips, _ := net.LookupHost(s)
	return ips[0] + ":" + *port
}

func main() {
	flag.Parse()

	db, err := sql.Open("postgres", *db_conn)
	if err != nil {
		fmt.Println("ERROR Opening DB Connection")
		fmt.Println(err)
		return
	}

	Dbm := &gorp.DbMap{
		Db:      db,
		Dialect: gorp.PostgresDialect{},
	}
	models.CreateTables(Dbm)

	handler := &melangeServer{
		Map: Dbm,
	}

	loadedKey, err := identity.LoadKeyFromFile(*key_file)
	if err != nil {
		loadedKey, err = identity.CreateIdentity()
		if err != nil {
			handler.HandleError(&server.ServerError{"Creating Mailserver Key", err})
			return
		}
		if *key_file != "" {
			err = loadedKey.SaveKeyToFile(*key_file)
			if err != nil {
				handler.HandleError(&server.ServerError{"Saving Mailserver Key", err})
				return
			}
		}
	}
	handler.LogMessage("Loaded Address", loadedKey.Address.String())

	mailserver.InitRouter()
	adServer := server.Server{
		LocationName: *me,
		Key:          loadedKey,
		Delegate:     handler,
		Router:       mailserver.LookupRouter,
	}
	StartServer(adServer, handler)
}

func StartServer(theServer server.Server, handler *melangeServer) {
	err := theServer.StartServer(*port)
	if err != nil {
		handler.HandleError(&server.ServerError{"Starting Server", err})
		os.Exit(1)
	}
}

type melangeServer struct {
	Map *gorp.DbMap
	server.BasicServer
}

func (m *melangeServer) SaveMessageDescription(alert *server.MessageDescription) {
	a := models.CreateAlertFromDescription(alert)
	err := m.Map.Insert(a)
	if err != nil {
		m.HandleError(&server.ServerError{"Saving new alert to db.", err})
	}
}

func (m *melangeServer) IdentityForUser(addr *identity.Address) *identity.Identity {
	return models.IdentityFromFingerprint(addr.String(), m.Map)
}

func (m *melangeServer) RetrieveMessageForUser(name string, author *identity.Address, forAddr *identity.Address) *message.Mail {
	var results []*models.Message
	_, err := m.Map.Select(&results, "select * from dispatch_messages where name = $1 and \"from\" = $2", name, author.String())
	if err != nil {
		m.HandleError(&server.ServerError{"Finding Messages for User", err})
		return nil
	}
	if len(results) != 1 {
		m.HandleError(&server.ServerError{"Finding Messages for User", errors.New("Found wrong number of messages.")})
		return nil
	}

	out, err := results[0].ToDispatch(m.Map, forAddr.String())
	if err != nil {
		m.HandleError(&server.ServerError{"Creating Dispatch Message", err})
		return nil
	}
	return out
}

func (m *melangeServer) RetrieveMessageListForUser(since uint64, author *identity.Address, forAddr *identity.Address) *server.MessageList {
	var results []*models.Message
	_, err := m.Map.Select(&results, "select * from dispatch_messages where \"from\" = $1 and timestamp > $2", author.String(), since)
	if err != nil {
		m.HandleError(&server.ServerError{"Loading messages from DB", err})
		return nil
	}

	out := server.CreateMessageList(author, forAddr)

	for _, v := range results {
		desc, err := v.ToDescription(forAddr.String())
		if err != nil {
			m.HandleError(&server.ServerError{"Loading description.", err})
			return nil
		}
		out.AddMessageDescription(desc)
	}

	return out
}
<commit_msg>Fixed Mailserver Location Issue<commit_after>package main

import (
	"airdispat.ch/identity"
	"airdispat.ch/message"
	"airdispat.ch/server"
	"database/sql"
	"errors"
	"flag"
	"fmt"
	"github.com/coopernurse/gorp"
	_ "github.com/lib/pq"
	"melange/app/models"
	"melange/mailserver"
	"net"
	"os"
)

var port = flag.String("port", "2048", "select the port on which to run the mail server")
var me = flag.String("me", getServerLocation(), "the location of the server that should be broadcast")

var key_file = flag.String("key", "", "the file to store keys")

var db_conn = flag.String("db", "", "the connection string for db.Open")

func getServerLocation() string {
	s, _ := os.Hostname()
	ips, _ := net.LookupHost(s)
	return ips[0] + ":" + *port
}

func main() {
	flag.Parse()

	db, err := sql.Open("postgres", *db_conn)
	if err != nil {
		fmt.Println("ERROR Opening DB Connection")
		fmt.Println(err)
		return
	}

	Dbm := &gorp.DbMap{
		Db:      db,
		Dialect: gorp.PostgresDialect{},
	}
	models.CreateTables(Dbm)

	handler := &melangeServer{
		Map: Dbm,
	}

	loadedKey, err := identity.LoadKeyFromFile(*key_file)
	if err != nil {
		loadedKey, err = identity.CreateIdentity()
		if err != nil {
			handler.HandleError(&server.ServerError{"Creating Mailserver Key", err})
			return
		}
		if *key_file != "" {
			err = loadedKey.SaveKeyToFile(*key_file)
			if err != nil {
				handler.HandleError(&server.ServerError{"Saving Mailserver Key", err})
				return
			}
		}
	}
	handler.LogMessage("Loaded Address", loadedKey.Address.String())

	mailserver.InitRouter()
	adServer := server.Server{
		LocationName: *me,
		Key:          loadedKey,
		Delegate:     handler,
		Router:       mailserver.LookupRouter,
	}
	StartServer(adServer, handler)
}

func StartServer(theServer server.Server, handler *melangeServer) {
	err := theServer.StartServer(*port)
	if err != nil {
		handler.HandleError(&server.ServerError{"Starting Server", err})
		os.Exit(1)
	}
}

type melangeServer struct {
	Map *gorp.DbMap
	server.BasicServer
}

func (m *melangeServer) SaveMessageDescription(alert *server.MessageDescription) {
	a := models.CreateAlertFromDescription(alert)
	err := m.Map.Insert(a)
	if err != nil {
		m.HandleError(&server.ServerError{"Saving new alert to db.", err})
	}
}

func (m *melangeServer) IdentityForUser(addr *identity.Address) *identity.Identity {
	return models.IdentityFromFingerprint(addr.String(), m.Map)
}

func (m *melangeServer) RetrieveMessageForUser(name string, author *identity.Address, forAddr *identity.Address) *message.Mail {
	var results []*models.Message
	_, err := m.Map.Select(&results, "select * from dispatch_messages where name = $1 and \"from\" = $2", name, author.String())
	if err != nil {
		m.HandleError(&server.ServerError{"Finding Messages for User", err})
		return nil
	}
	if len(results) != 1 {
		m.HandleError(&server.ServerError{"Finding Messages for User", errors.New("Found wrong number of messages.")})
		return nil
	}

	out, err := results[0].ToDispatch(m.Map, forAddr.String())
	if err != nil {
		m.HandleError(&server.ServerError{"Creating Dispatch Message", err})
		return nil
	}
	return out
}

func (m *melangeServer) RetrieveMessageListForUser(since uint64, author *identity.Address, forAddr *identity.Address) *server.MessageList {
	var results []*models.Message
	_, err := m.Map.Select(&results, "select * from dispatch_messages where \"from\" = $1 and timestamp > $2", author.String(), since)
	if err != nil {
		m.HandleError(&server.ServerError{"Loading messages from DB", err})
		return nil
	}

	out := server.CreateMessageList(author, forAddr)

	for _, v := range results {
		desc, err := v.ToDescription(forAddr.String())
		if err != nil {
			m.HandleError(&server.ServerError{"Loading description.", err})
			return nil
		}
		desc.Location = *me
		out.AddMessageDescription(desc)
	}

	return out
}
<|endoftext|>


<commit_before>package strand

import (
	"github.com/bcurren/go-hue"
	"github.com/bcurren/go-hue/huetest"
	"strings"
	"testing"
)

func Test_NewLightStrand(t *testing.T) {
	stubHueAPI := &huetest.StubAPI{}
	lightStrand := NewLightStrand(3, stubHueAPI)

	if lightStrand.Length != 3 {
		t.Error("Light strand should have length given in constructor.")
	}
	if lightStrand.Lights.Length() != 0 {
		t.Error("Light strand should have Lights initialized to empty.")
	}
}

func Test_MapUnmappedLights(t *testing.T) {
	hueLights := make([]hue.Light, 1, 1)
	hueLights[0].Id = "3"

	stubHueAPI := &huetest.StubAPI{}
	stubHueAPI.GetLightsError = nil
	stubHueAPI.GetLightsReturn = hueLights

	lightStrand := NewLightStrand(1, stubHueAPI)

	countTimesCalled := 0
	err := lightStrand.MapUnmappedLights(func(lightId string) string {
		countTimesCalled += 1

		// Callback param is light id
		if lightId != "3" {
			t.Error("Callback parameter should have lightId 3.")
		}
		// Should have set hue light to red
		if stubHueAPI.SetLightStateParamLightId != "3" {
			t.Error("Should have set light 3.")
		}
		if stubHueAPI.SetLightStateParamLightState.Hue == nil {
			t.Error("Should have set the state.")
		}

		return "1"
	})
	if err != nil {
		t.Fatal("Error returned when mapping")
	}

	if countTimesCalled != 1 {
		t.Error("Map function called more than 1 time.")
	}
	if lightStrand.Lights.GetValue("1") != "3" {
		t.Error("Didn't map to the correct id.")
	}

	// Should have set hue light to white
	if stubHueAPI.SetLightStateParamLightId != "3" {
		t.Error("Should have set light 3.")
	}
	if stubHueAPI.SetLightStateParamLightState.ColorTemp == nil {
		t.Error("Should have set the state.")
	}
}

func Test_MapUnmappedLightsSkipXSocketIds(t *testing.T) {
	hueLights := make([]hue.Light, 1, 1)
	hueLights[0].Id = "3"

	stubHueAPI := &huetest.StubAPI{}
	stubHueAPI.GetLightsError = nil
	stubHueAPI.GetLightsReturn = hueLights

	lightStrand := NewLightStrand(3, stubHueAPI)
	err := lightStrand.MapUnmappedLights(func(string) string {
		return "x"
	})
	if err != nil {
		t.Fatal("Error returned when mapping")
	}

	if lightStrand.Lights.Length() != 0 {
		t.Error("Should skip mapping when socket id is 'x'.")
	}
}

func Test_getUnmappedLightIds(t *testing.T) {
	hueLights := make([]hue.Light, 4, 4)
	hueLights[0].Id = "light3"
	hueLights[1].Id = "light1"
	hueLights[2].Id = "light5"
	hueLights[3].Id = "light2"

	stubHueAPI := &huetest.StubAPI{}
	stubHueAPI.GetLightsError = nil
	stubHueAPI.GetLightsReturn = hueLights

	lightStrand := NewLightStrand(3, stubHueAPI)
	lightStrand.Lights.Set("1", "light3")
	lightStrand.Lights.Set("2", "light2")
	lightStrand.Lights.Set("3", "light1")

	expected := []string{"light5"}
	actual, _ := lightStrand.getUnmappedLightIds()
	if !stringSlicesEqual(expected, actual) {
		t.Errorf("Expected a slice of all unmapped light ids. Expected %v but received %v.\n", expected, actual)
	}
}

// A function to test if two string slices are equal. If you know a better way, please
// update this function. Seems like there should be a beeter way but I couldn't find one.
func stringSlicesEqual(slice1 []string, slice2 []string) bool {
	// Check both nil or both not nil
	if slice1 == nil && slice2 == nil {
		return true
	} else if slice1 == nil && slice2 != nil {
		return false
	} else if slice1 != nil && slice2 == nil {
		return false
	}

	// Length must be the same
	if len(slice1) != len(slice2) {
		return false
	}

	// Contents must be the same
	sep := "|||"
	slices1String := strings.Join(slice1, sep)
	slices2String := strings.Join(slice2, sep)
	if slices1String != slices2String {
		return false
	}

	return true
}

func Test_validSocketId(t *testing.T) {
	lightStrand := NewLightStrand(3, nil)
	if lightStrand.validSocketId("0") {
		t.Error("Socket id 0 should be invalid.")
	}
	if !lightStrand.validSocketId("1") {
		t.Error("Socket id 1 should be valid.")
	}
	if !lightStrand.validSocketId("3") {
		t.Error("Socket id 3 should be valid.")
	}
	if lightStrand.validSocketId("4") {
		t.Error("Socket id 4 should be invalid.")
	}
	if lightStrand.validSocketId("notint") {
		t.Error("Socket id that is not an int should be invalid.")
	}
}
<commit_msg>added test case for missing value in map<commit_after>package strand

import (
	"github.com/bcurren/go-hue"
	"github.com/bcurren/go-hue/huetest"
	"strings"
	"testing"
)

func Test_NewLightStrand(t *testing.T) {
	stubHueAPI := &huetest.StubAPI{}
	lightStrand := NewLightStrand(3, stubHueAPI)

	if lightStrand.Length != 3 {
		t.Error("Light strand should have length given in constructor.")
	}
	if lightStrand.Lights.Length() != 0 {
		t.Error("Light strand should have Lights initialized to empty.")
	}
}

func Test_MapUnmappedLights(t *testing.T) {
	hueLights := make([]hue.Light, 1, 1)
	hueLights[0].Id = "3"

	stubHueAPI := &huetest.StubAPI{}
	stubHueAPI.GetLightsError = nil
	stubHueAPI.GetLightsReturn = hueLights

	lightStrand := NewLightStrand(1, stubHueAPI)

	countTimesCalled := 0
	err := lightStrand.MapUnmappedLights(func(lightId string) string {
		countTimesCalled += 1

		// Callback param is light id
		if lightId != "3" {
			t.Error("Callback parameter should have lightId 3.")
		}
		// Should have set hue light to red
		if stubHueAPI.SetLightStateParamLightId != "3" {
			t.Error("Should have set light 3.")
		}
		if stubHueAPI.SetLightStateParamLightState.Hue == nil {
			t.Error("Should have set the state.")
		}

		return "1"
	})
	if err != nil {
		t.Fatal("Error returned when mapping")
	}

	if countTimesCalled != 1 {
		t.Error("Map function called more than 1 time.")
	}
	if lightStrand.Lights.GetValue("1") != "3" {
		t.Error("Didn't map to the correct id.")
	}

	// Should have set hue light to white
	if stubHueAPI.SetLightStateParamLightId != "3" {
		t.Error("Should have set light 3.")
	}
	if stubHueAPI.SetLightStateParamLightState.ColorTemp == nil {
		t.Error("Should have set the state.")
	}
}

func Test_MapUnmappedLightsSkipXSocketIds(t *testing.T) {
	hueLights := make([]hue.Light, 1, 1)
	hueLights[0].Id = "3"

	stubHueAPI := &huetest.StubAPI{}
	stubHueAPI.GetLightsError = nil
	stubHueAPI.GetLightsReturn = hueLights

	lightStrand := NewLightStrand(3, stubHueAPI)
	err := lightStrand.MapUnmappedLights(func(string) string {
		return "x"
	})
	if err != nil {
		t.Fatal("Error returned when mapping")
	}

	if lightStrand.Lights.Length() != 0 {
		t.Error("Should skip mapping when socket id is 'x'.")
	}
}

func Test_getUnmappedLightIds(t *testing.T) {
	hueLights := make([]hue.Light, 4, 4)
	hueLights[0].Id = "light3"
	hueLights[1].Id = "light1"
	hueLights[2].Id = "light5"
	hueLights[3].Id = "light2"

	stubHueAPI := &huetest.StubAPI{}
	stubHueAPI.GetLightsError = nil
	stubHueAPI.GetLightsReturn = hueLights

	lightStrand := NewLightStrand(3, stubHueAPI)
	lightStrand.Lights.Set("1", "light3")
	lightStrand.Lights.Set("2", "light2")
	lightStrand.Lights.Set("3", "light1")
	lightStrand.Lights.Set("4", "missinglight")

	expected := []string{"light5"}
	actual, _ := lightStrand.getUnmappedLightIds()
	if !stringSlicesEqual(expected, actual) {
		t.Errorf("Expected a slice of all unmapped light ids. Expected %v but received %v.\n", expected, actual)
	}
}

// A function to test if two string slices are equal. If you know a better way, please
// update this function. Seems like there should be a beeter way but I couldn't find one.
func stringSlicesEqual(slice1 []string, slice2 []string) bool {
	// Check both nil or both not nil
	if slice1 == nil && slice2 == nil {
		return true
	} else if slice1 == nil && slice2 != nil {
		return false
	} else if slice1 != nil && slice2 == nil {
		return false
	}

	// Length must be the same
	if len(slice1) != len(slice2) {
		return false
	}

	// Contents must be the same
	sep := "|||"
	slices1String := strings.Join(slice1, sep)
	slices2String := strings.Join(slice2, sep)
	if slices1String != slices2String {
		return false
	}

	return true
}

func Test_validSocketId(t *testing.T) {
	lightStrand := NewLightStrand(3, nil)
	if lightStrand.validSocketId("0") {
		t.Error("Socket id 0 should be invalid.")
	}
	if !lightStrand.validSocketId("1") {
		t.Error("Socket id 1 should be valid.")
	}
	if !lightStrand.validSocketId("3") {
		t.Error("Socket id 3 should be valid.")
	}
	if lightStrand.validSocketId("4") {
		t.Error("Socket id 4 should be invalid.")
	}
	if lightStrand.validSocketId("notint") {
		t.Error("Socket id that is not an int should be invalid.")
	}
}
<|endoftext|>


<commit_before>package agent

import (
	"archive/tar"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"

	"github.com/pkg/errors"

	"github.com/havoc-io/mutagen/pkg/process"
)

const (
	agentBundleName = "mutagen-agents.tar.gz"
)

// TODO: Note that this file will not mark the resultant file as executable.
func executableForPlatform(goos, goarch string) (string, error) {
	// Compute the path to the current executable.
	executablePath, err := os.Executable()
	if err != nil {
		return "", errors.Wrap(err, "unable to determine executable path")
	}

	// Compute the path to the agent bundle.
	bundlePath := filepath.Join(filepath.Dir(executablePath), agentBundleName)

	// Open the bundle path and ensure its closure.
	bundle, err := os.Open(bundlePath)
	if err != nil {
		return "", errors.Wrap(err, "unable to open agent bundle")
	}
	defer bundle.Close()

	// Create a decompressor and ensure its closure.
	bundleDecompressor, err := gzip.NewReader(bundle)
	if err != nil {
		return "", errors.Wrap(err, "unable to decompress agent bundle")
	}
	defer bundleDecompressor.Close()

	// Create an archive reader.
	bundleArchive := tar.NewReader(bundleDecompressor)

	// Scan until we find a matching header.
	var header *tar.Header
	for {
		if h, err := bundleArchive.Next(); err != nil {
			if err == io.EOF {
				break
			}
			return "", errors.Wrap(err, "unable to read archive header")
		} else if h.Name == fmt.Sprintf("%s_%s", goos, goarch) {
			header = h
			break
		}
	}

	// Check if we have a valid header. If not, there was no match.
	if header == nil {
		return "", errors.New("unsupported platform")
	}

	// Compute the base name for the output file.
	targetBaseName := process.ExecutableName(agentBaseName, goos)

	// Create a temporary file in which to receive the agent on disk.
	file, err := ioutil.TempFile("", targetBaseName)
	if err != nil {
		return "", errors.Wrap(err, "unable to create temporary file")
	}

	// Copy data into the file.
	if _, err := io.CopyN(file, bundleArchive, header.Size); err != nil {
		file.Close()
		os.Remove(file.Name())
		return "", errors.Wrap(err, "unable to copy agent data")
	}

	// If we're not on Windows and our target system is not Windows, mark the
	// file as executable. This will save us an additional "chmod +x" command
	// during agent installation.
	if runtime.GOOS != "windows" && goos != "windows" {
		if err := file.Chmod(0700); err != nil {
			file.Close()
			os.Remove(file.Name())
			return "", errors.Wrap(err, "unable to make agent executable")
		}
	}

	// Close the file.
	if err := file.Close(); err != nil {
		return "", errors.Wrap(err, "unable to close temporary file")
	}

	// Success.
	return file.Name(), nil
}
<commit_msg>Removed outdated comment.<commit_after>package agent

import (
	"archive/tar"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"

	"github.com/pkg/errors"

	"github.com/havoc-io/mutagen/pkg/process"
)

const (
	agentBundleName = "mutagen-agents.tar.gz"
)

func executableForPlatform(goos, goarch string) (string, error) {
	// Compute the path to the current executable.
	executablePath, err := os.Executable()
	if err != nil {
		return "", errors.Wrap(err, "unable to determine executable path")
	}

	// Compute the path to the agent bundle.
	bundlePath := filepath.Join(filepath.Dir(executablePath), agentBundleName)

	// Open the bundle path and ensure its closure.
	bundle, err := os.Open(bundlePath)
	if err != nil {
		return "", errors.Wrap(err, "unable to open agent bundle")
	}
	defer bundle.Close()

	// Create a decompressor and ensure its closure.
	bundleDecompressor, err := gzip.NewReader(bundle)
	if err != nil {
		return "", errors.Wrap(err, "unable to decompress agent bundle")
	}
	defer bundleDecompressor.Close()

	// Create an archive reader.
	bundleArchive := tar.NewReader(bundleDecompressor)

	// Scan until we find a matching header.
	var header *tar.Header
	for {
		if h, err := bundleArchive.Next(); err != nil {
			if err == io.EOF {
				break
			}
			return "", errors.Wrap(err, "unable to read archive header")
		} else if h.Name == fmt.Sprintf("%s_%s", goos, goarch) {
			header = h
			break
		}
	}

	// Check if we have a valid header. If not, there was no match.
	if header == nil {
		return "", errors.New("unsupported platform")
	}

	// Compute the base name for the output file.
	targetBaseName := process.ExecutableName(agentBaseName, goos)

	// Create a temporary file in which to receive the agent on disk.
	file, err := ioutil.TempFile("", targetBaseName)
	if err != nil {
		return "", errors.Wrap(err, "unable to create temporary file")
	}

	// Copy data into the file.
	if _, err := io.CopyN(file, bundleArchive, header.Size); err != nil {
		file.Close()
		os.Remove(file.Name())
		return "", errors.Wrap(err, "unable to copy agent data")
	}

	// If we're not on Windows and our target system is not Windows, mark the
	// file as executable. This will save us an additional "chmod +x" command
	// during agent installation.
	if runtime.GOOS != "windows" && goos != "windows" {
		if err := file.Chmod(0700); err != nil {
			file.Close()
			os.Remove(file.Name())
			return "", errors.Wrap(err, "unable to make agent executable")
		}
	}

	// Close the file.
	if err := file.Close(); err != nil {
		return "", errors.Wrap(err, "unable to close temporary file")
	}

	// Success.
	return file.Name(), nil
}
<|endoftext|>


<commit_before>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package compose

import (
	"context"

	"github.com/docker/compose/v2/pkg/api"
	"github.com/docker/compose/v2/pkg/progress"
)

func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error {
	return progress.Run(ctx, func(ctx context.Context) error {
		return s.stop(ctx, projectName, options)
	})
}

func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error {
	w := progress.ContextWriter(ctx)

	services := options.Services
	if len(services) == 0 {
		services = []string{}
	}

	var containers Containers
	containers, err := s.getContainers(ctx, projectName, oneOffInclude, true, services...)
	if err != nil {
		return err
	}

	project, err := s.projectFromName(containers, projectName, services...)
	if err != nil {
		return err
	}

	return InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
		return s.stopContainers(ctx, w, containers.filter(isService(service)), options.Timeout)
	})
}
<commit_msg>don't failed when trying to stop or rm services with no containers running<commit_after>/*
   Copyright 2020 Docker Compose CLI authors

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package compose

import (
	"context"

	"github.com/docker/compose/v2/pkg/api"
	"github.com/docker/compose/v2/pkg/progress"
)

func (s *composeService) Stop(ctx context.Context, projectName string, options api.StopOptions) error {
	return progress.Run(ctx, func(ctx context.Context) error {
		return s.stop(ctx, projectName, options)
	})
}

func (s *composeService) stop(ctx context.Context, projectName string, options api.StopOptions) error {
	w := progress.ContextWriter(ctx)

	services := options.Services
	if len(services) == 0 {
		services = []string{}
	}

	var containers Containers
	containers, err := s.getContainers(ctx, projectName, oneOffInclude, true, services...)
	if err != nil {
		return err
	}

	project, err := s.projectFromName(containers, projectName, services...)
	if err != nil && !api.IsNotFoundError(err) {
		return err
	}

	return InReverseDependencyOrder(ctx, project, func(c context.Context, service string) error {
		return s.stopContainers(ctx, w, containers.filter(isService(service)), options.Timeout)
	})
}
<|endoftext|>


<commit_before>package querier

import (
	"fmt"
	"sync"
	"time"

	"github.com/cortexproject/cortex/pkg/util"
	"github.com/go-kit/kit/log/level"
	"github.com/grafana/loki/pkg/iter"
	"github.com/grafana/loki/pkg/logproto"
	"github.com/pkg/errors"
)

const (
	// keep checking connections with ingesters in duration
	checkConnectionsWithIngestersPeriod = time.Second * 5

	// the size of the channel buffer used to send tailing streams
	// back to the requesting client
	maxBufferedTailResponses = 10

	// the maximum number of entries to return in a TailResponse
	maxEntriesPerTailResponse = 100

	// the maximum number of dropped entries to keep in memory that will be sent along
	// with the next successfully pushed response. Once the dropped entries memory buffer
	// exceed this value, we start skipping dropped entries too.
	maxDroppedEntriesPerTailResponse = 1000
)

type droppedEntry struct {
	Timestamp time.Time
	Labels    string
}

// TailResponse holds response sent by tailer
type TailResponse struct {
	Streams        []logproto.Stream `json:"streams"`
	DroppedEntries []droppedEntry    `json:"dropped_entries"`
}

// Tailer manages complete lifecycle of a tail request
type Tailer struct {
	// openStreamIterator is for streams already open
	openStreamIterator iter.HeapIterator
	streamMtx          sync.Mutex // for synchronizing access to openStreamIterator

	currEntry  logproto.Entry
	currLabels string

	tailDisconnectedIngesters func([]string) (map[string]logproto.Querier_TailClient, error)

	querierTailClients    map[string]logproto.Querier_TailClient // addr -> grpc clients for tailing logs from ingesters
	querierTailClientsMtx sync.RWMutex

	stopped         bool
	delayFor        time.Duration
	responseChan    chan *TailResponse
	closeErrChan    chan error
	tailMaxDuration time.Duration

	// if we are not seeing any response from ingester,
	// how long do we want to wait by going into sleep
	waitEntryThrottle time.Duration
}

func (t *Tailer) readTailClients() {
	t.querierTailClientsMtx.RLock()
	defer t.querierTailClientsMtx.RUnlock()

	for addr, querierTailClient := range t.querierTailClients {
		go t.readTailClient(addr, querierTailClient)
	}
}

// keeps sending oldest entry to responseChan. If channel is blocked drop the entry
// When channel is unblocked, send details of dropped entries with current entry
func (t *Tailer) loop() {
	checkConnectionTicker := time.NewTicker(checkConnectionsWithIngestersPeriod)
	defer checkConnectionTicker.Stop()

	tailMaxDurationTicker := time.NewTicker(t.tailMaxDuration)
	defer tailMaxDurationTicker.Stop()

	droppedEntries := make([]droppedEntry, 0)

	for !t.stopped {
		select {
		case <-checkConnectionTicker.C:
			// Try to reconnect dropped ingesters and connect to new ingesters
			if err := t.checkIngesterConnections(); err != nil {
				level.Error(util.Logger).Log("msg", "Error reconnecting to disconnected ingesters", "err", err)
			}
		case <-tailMaxDurationTicker.C:
			if err := t.close(); err != nil {
				level.Error(util.Logger).Log("msg", "Error closing Tailer", "err", err)
			}
			t.closeErrChan <- errors.New("reached tail max duration limit")
			return
		default:
		}

		// Read as much entries as we can (up to the max allowed) and populate the
		// tail response we'll send over the response channel
		tailResponse := new(TailResponse)
		entriesCount := 0

		for ; entriesCount < maxEntriesPerTailResponse && t.next(); entriesCount++ {
			// If the response channel channel is blocked, we drop the current entry directly
			// to save the effort
			if t.isResponseChanBlocked() {
				droppedEntries = dropEntry(droppedEntries, t.currEntry.Timestamp, t.currLabels)
				continue
			}

			tailResponse.Streams = append(tailResponse.Streams, logproto.Stream{
				Labels:  t.currLabels,
				Entries: []logproto.Entry{t.currEntry},
			})
		}

		// If all consumed entries have been dropped because the response channel is blocked
		// we should reiterate on the loop
		if len(tailResponse.Streams) == 0 && entriesCount > 0 {
			continue
		}

		// If no entry has been consumed we should ensure it's not caused by all ingesters
		// connections dropped and then throttle for a while
		if len(tailResponse.Streams) == 0 {
			t.querierTailClientsMtx.RLock()
			numClients := len(t.querierTailClients)
			t.querierTailClientsMtx.RUnlock()

			if numClients == 0 {
				// All the connections to ingesters are dropped, try reconnecting or return error
				if err := t.checkIngesterConnections(); err != nil {
					level.Error(util.Logger).Log("Error reconnecting to ingesters", fmt.Sprintf("%v", err))
				} else {
					continue
				}
				if err := t.close(); err != nil {
					level.Error(util.Logger).Log("Error closing Tailer", fmt.Sprintf("%v", err))
				}
				t.closeErrChan <- errors.New("all ingesters closed the connection")
				return
			}

			time.Sleep(t.waitEntryThrottle)
			continue
		}

		// Send the tail response through the response channel without blocking.
		// Drop the entry if the response channel buffer is full.
		if len(droppedEntries) > 0 {
			tailResponse.DroppedEntries = droppedEntries
		}

		select {
		case t.responseChan <- tailResponse:
			if len(droppedEntries) > 0 {
				droppedEntries = make([]droppedEntry, 0)
			}
		default:
			droppedEntries = dropEntries(droppedEntries, tailResponse.Streams)
		}
	}
}

// Checks whether we are connected to all the ingesters to tail the logs.
// Helps in connecting to disconnected ingesters or connecting to new ingesters
func (t *Tailer) checkIngesterConnections() error {
	t.querierTailClientsMtx.Lock()
	defer t.querierTailClientsMtx.Unlock()

	connectedIngestersAddr := make([]string, 0, len(t.querierTailClients))
	for addr := range t.querierTailClients {
		connectedIngestersAddr = append(connectedIngestersAddr, addr)
	}

	newConnections, err := t.tailDisconnectedIngesters(connectedIngestersAddr)
	if err != nil {
		return err
	}

	if len(newConnections) != 0 {
		for addr, tailClient := range newConnections {
			t.querierTailClients[addr] = tailClient
			go t.readTailClient(addr, tailClient)
		}
	}
	return nil
}

// removes disconnected tail client from map
func (t *Tailer) dropTailClient(addr string) {
	t.querierTailClientsMtx.Lock()
	defer t.querierTailClientsMtx.Unlock()

	delete(t.querierTailClients, addr)
}

// keeps reading streams from grpc connection with ingesters
func (t *Tailer) readTailClient(addr string, querierTailClient logproto.Querier_TailClient) {
	var resp *logproto.TailResponse
	var err error
	defer t.dropTailClient(addr)
	for {
		if t.stopped {
			if err := querierTailClient.CloseSend(); err != nil {
				level.Error(util.Logger).Log("Error closing gprc tail client", fmt.Sprintf("%v", err))
			}
			break
		}
		resp, err = querierTailClient.Recv()
		if err != nil {
			level.Error(util.Logger).Log("Error receiving response from gprc tail client", fmt.Sprintf("%v", err))
			break
		}
		t.pushTailResponseFromIngester(resp)
	}
}

// pushes new streams from ingesters synchronously
func (t *Tailer) pushTailResponseFromIngester(resp *logproto.TailResponse) {
	t.streamMtx.Lock()
	defer t.streamMtx.Unlock()

	t.openStreamIterator.Push(iter.NewStreamIterator(resp.Stream))
}

// finds oldest entry by peeking at open stream iterator.
// Response from ingester is pushed to open stream for further processing
func (t *Tailer) next() bool {
	t.streamMtx.Lock()
	defer t.streamMtx.Unlock()

	if t.openStreamIterator.Len() == 0 || !time.Now().After(t.openStreamIterator.Peek().Add(t.delayFor)) || !t.openStreamIterator.Next() {
		return false
	}

	t.currEntry = t.openStreamIterator.Entry()
	t.currLabels = t.openStreamIterator.Labels()
	return true
}

func (t *Tailer) close() error {
	t.streamMtx.Lock()
	defer t.streamMtx.Unlock()

	t.stopped = true
	return t.openStreamIterator.Close()
}

func (t *Tailer) isResponseChanBlocked() bool {
	// Thread-safety: len() and cap() on a channel are thread-safe. The cap() doesn't
	// change over the time, while len() does.
	return len(t.responseChan) == cap(t.responseChan)
}

func (t *Tailer) getResponseChan() <-chan *TailResponse {
	return t.responseChan
}

func (t *Tailer) getCloseErrorChan() <-chan error {
	return t.closeErrChan
}

func newTailer(
	delayFor time.Duration,
	querierTailClients map[string]logproto.Querier_TailClient,
	historicEntries iter.EntryIterator,
	tailDisconnectedIngesters func([]string) (map[string]logproto.Querier_TailClient, error),
	tailMaxDuration time.Duration,
	waitEntryThrottle time.Duration,
) *Tailer {
	t := Tailer{
		openStreamIterator:        iter.NewHeapIterator([]iter.EntryIterator{historicEntries}, logproto.FORWARD),
		querierTailClients:        querierTailClients,
		delayFor:                  delayFor,
		responseChan:              make(chan *TailResponse, maxBufferedTailResponses),
		closeErrChan:              make(chan error),
		tailDisconnectedIngesters: tailDisconnectedIngesters,
		tailMaxDuration:           tailMaxDuration,
		waitEntryThrottle:         waitEntryThrottle,
	}

	t.readTailClients()
	go t.loop()
	return &t
}

func dropEntry(droppedEntries []droppedEntry, timestamp time.Time, labels string) []droppedEntry {
	if len(droppedEntries) >= maxDroppedEntriesPerTailResponse {
		return droppedEntries
	}

	return append(droppedEntries, droppedEntry{timestamp, labels})
}

func dropEntries(droppedEntries []droppedEntry, streams []logproto.Stream) []droppedEntry {
	for _, stream := range streams {
		for _, entry := range stream.Entries {
			droppedEntries = dropEntry(droppedEntries, entry.Timestamp, entry.Line)
		}
	}

	return droppedEntries
}
<commit_msg>fix typo in error messages (#1030)<commit_after>package querier

import (
	"fmt"
	"sync"
	"time"

	"github.com/cortexproject/cortex/pkg/util"
	"github.com/go-kit/kit/log/level"
	"github.com/grafana/loki/pkg/iter"
	"github.com/grafana/loki/pkg/logproto"
	"github.com/pkg/errors"
)

const (
	// keep checking connections with ingesters in duration
	checkConnectionsWithIngestersPeriod = time.Second * 5

	// the size of the channel buffer used to send tailing streams
	// back to the requesting client
	maxBufferedTailResponses = 10

	// the maximum number of entries to return in a TailResponse
	maxEntriesPerTailResponse = 100

	// the maximum number of dropped entries to keep in memory that will be sent along
	// with the next successfully pushed response. Once the dropped entries memory buffer
	// exceed this value, we start skipping dropped entries too.
	maxDroppedEntriesPerTailResponse = 1000
)

type droppedEntry struct {
	Timestamp time.Time
	Labels    string
}

// TailResponse holds response sent by tailer
type TailResponse struct {
	Streams        []logproto.Stream `json:"streams"`
	DroppedEntries []droppedEntry    `json:"dropped_entries"`
}

// Tailer manages complete lifecycle of a tail request
type Tailer struct {
	// openStreamIterator is for streams already open
	openStreamIterator iter.HeapIterator
	streamMtx          sync.Mutex // for synchronizing access to openStreamIterator

	currEntry  logproto.Entry
	currLabels string

	tailDisconnectedIngesters func([]string) (map[string]logproto.Querier_TailClient, error)

	querierTailClients    map[string]logproto.Querier_TailClient // addr -> grpc clients for tailing logs from ingesters
	querierTailClientsMtx sync.RWMutex

	stopped         bool
	delayFor        time.Duration
	responseChan    chan *TailResponse
	closeErrChan    chan error
	tailMaxDuration time.Duration

	// if we are not seeing any response from ingester,
	// how long do we want to wait by going into sleep
	waitEntryThrottle time.Duration
}

func (t *Tailer) readTailClients() {
	t.querierTailClientsMtx.RLock()
	defer t.querierTailClientsMtx.RUnlock()

	for addr, querierTailClient := range t.querierTailClients {
		go t.readTailClient(addr, querierTailClient)
	}
}

// keeps sending oldest entry to responseChan. If channel is blocked drop the entry
// When channel is unblocked, send details of dropped entries with current entry
func (t *Tailer) loop() {
	checkConnectionTicker := time.NewTicker(checkConnectionsWithIngestersPeriod)
	defer checkConnectionTicker.Stop()

	tailMaxDurationTicker := time.NewTicker(t.tailMaxDuration)
	defer tailMaxDurationTicker.Stop()

	droppedEntries := make([]droppedEntry, 0)

	for !t.stopped {
		select {
		case <-checkConnectionTicker.C:
			// Try to reconnect dropped ingesters and connect to new ingesters
			if err := t.checkIngesterConnections(); err != nil {
				level.Error(util.Logger).Log("msg", "Error reconnecting to disconnected ingesters", "err", err)
			}
		case <-tailMaxDurationTicker.C:
			if err := t.close(); err != nil {
				level.Error(util.Logger).Log("msg", "Error closing Tailer", "err", err)
			}
			t.closeErrChan <- errors.New("reached tail max duration limit")
			return
		default:
		}

		// Read as much entries as we can (up to the max allowed) and populate the
		// tail response we'll send over the response channel
		tailResponse := new(TailResponse)
		entriesCount := 0

		for ; entriesCount < maxEntriesPerTailResponse && t.next(); entriesCount++ {
			// If the response channel channel is blocked, we drop the current entry directly
			// to save the effort
			if t.isResponseChanBlocked() {
				droppedEntries = dropEntry(droppedEntries, t.currEntry.Timestamp, t.currLabels)
				continue
			}

			tailResponse.Streams = append(tailResponse.Streams, logproto.Stream{
				Labels:  t.currLabels,
				Entries: []logproto.Entry{t.currEntry},
			})
		}

		// If all consumed entries have been dropped because the response channel is blocked
		// we should reiterate on the loop
		if len(tailResponse.Streams) == 0 && entriesCount > 0 {
			continue
		}

		// If no entry has been consumed we should ensure it's not caused by all ingesters
		// connections dropped and then throttle for a while
		if len(tailResponse.Streams) == 0 {
			t.querierTailClientsMtx.RLock()
			numClients := len(t.querierTailClients)
			t.querierTailClientsMtx.RUnlock()

			if numClients == 0 {
				// All the connections to ingesters are dropped, try reconnecting or return error
				if err := t.checkIngesterConnections(); err != nil {
					level.Error(util.Logger).Log("Error reconnecting to ingesters", fmt.Sprintf("%v", err))
				} else {
					continue
				}
				if err := t.close(); err != nil {
					level.Error(util.Logger).Log("Error closing Tailer", fmt.Sprintf("%v", err))
				}
				t.closeErrChan <- errors.New("all ingesters closed the connection")
				return
			}

			time.Sleep(t.waitEntryThrottle)
			continue
		}

		// Send the tail response through the response channel without blocking.
		// Drop the entry if the response channel buffer is full.
		if len(droppedEntries) > 0 {
			tailResponse.DroppedEntries = droppedEntries
		}

		select {
		case t.responseChan <- tailResponse:
			if len(droppedEntries) > 0 {
				droppedEntries = make([]droppedEntry, 0)
			}
		default:
			droppedEntries = dropEntries(droppedEntries, tailResponse.Streams)
		}
	}
}

// Checks whether we are connected to all the ingesters to tail the logs.
// Helps in connecting to disconnected ingesters or connecting to new ingesters
func (t *Tailer) checkIngesterConnections() error {
	t.querierTailClientsMtx.Lock()
	defer t.querierTailClientsMtx.Unlock()

	connectedIngestersAddr := make([]string, 0, len(t.querierTailClients))
	for addr := range t.querierTailClients {
		connectedIngestersAddr = append(connectedIngestersAddr, addr)
	}

	newConnections, err := t.tailDisconnectedIngesters(connectedIngestersAddr)
	if err != nil {
		return err
	}

	if len(newConnections) != 0 {
		for addr, tailClient := range newConnections {
			t.querierTailClients[addr] = tailClient
			go t.readTailClient(addr, tailClient)
		}
	}
	return nil
}

// removes disconnected tail client from map
func (t *Tailer) dropTailClient(addr string) {
	t.querierTailClientsMtx.Lock()
	defer t.querierTailClientsMtx.Unlock()

	delete(t.querierTailClients, addr)
}

// keeps reading streams from grpc connection with ingesters
func (t *Tailer) readTailClient(addr string, querierTailClient logproto.Querier_TailClient) {
	var resp *logproto.TailResponse
	var err error
	defer t.dropTailClient(addr)
	for {
		if t.stopped {
			if err := querierTailClient.CloseSend(); err != nil {
				level.Error(util.Logger).Log("Error closing grpc tail client", fmt.Sprintf("%v", err))
			}
			break
		}
		resp, err = querierTailClient.Recv()
		if err != nil {
			level.Error(util.Logger).Log("Error receiving response from grpc tail client", fmt.Sprintf("%v", err))
			break
		}
		t.pushTailResponseFromIngester(resp)
	}
}

// pushes new streams from ingesters synchronously
func (t *Tailer) pushTailResponseFromIngester(resp *logproto.TailResponse) {
	t.streamMtx.Lock()
	defer t.streamMtx.Unlock()

	t.openStreamIterator.Push(iter.NewStreamIterator(resp.Stream))
}

// finds oldest entry by peeking at open stream iterator.
// Response from ingester is pushed to open stream for further processing
func (t *Tailer) next() bool {
	t.streamMtx.Lock()
	defer t.streamMtx.Unlock()

	if t.openStreamIterator.Len() == 0 || !time.Now().After(t.openStreamIterator.Peek().Add(t.delayFor)) || !t.openStreamIterator.Next() {
		return false
	}

	t.currEntry = t.openStreamIterator.Entry()
	t.currLabels = t.openStreamIterator.Labels()
	return true
}

func (t *Tailer) close() error {
	t.streamMtx.Lock()
	defer t.streamMtx.Unlock()

	t.stopped = true
	return t.openStreamIterator.Close()
}

func (t *Tailer) isResponseChanBlocked() bool {
	// Thread-safety: len() and cap() on a channel are thread-safe. The cap() doesn't
	// change over the time, while len() does.
	return len(t.responseChan) == cap(t.responseChan)
}

func (t *Tailer) getResponseChan() <-chan *TailResponse {
	return t.responseChan
}

func (t *Tailer) getCloseErrorChan() <-chan error {
	return t.closeErrChan
}

func newTailer(
	delayFor time.Duration,
	querierTailClients map[string]logproto.Querier_TailClient,
	historicEntries iter.EntryIterator,
	tailDisconnectedIngesters func([]string) (map[string]logproto.Querier_TailClient, error),
	tailMaxDuration time.Duration,
	waitEntryThrottle time.Duration,
) *Tailer {
	t := Tailer{
		openStreamIterator:        iter.NewHeapIterator([]iter.EntryIterator{historicEntries}, logproto.FORWARD),
		querierTailClients:        querierTailClients,
		delayFor:                  delayFor,
		responseChan:              make(chan *TailResponse, maxBufferedTailResponses),
		closeErrChan:              make(chan error),
		tailDisconnectedIngesters: tailDisconnectedIngesters,
		tailMaxDuration:           tailMaxDuration,
		waitEntryThrottle:         waitEntryThrottle,
	}

	t.readTailClients()
	go t.loop()
	return &t
}

func dropEntry(droppedEntries []droppedEntry, timestamp time.Time, labels string) []droppedEntry {
	if len(droppedEntries) >= maxDroppedEntriesPerTailResponse {
		return droppedEntries
	}

	return append(droppedEntries, droppedEntry{timestamp, labels})
}

func dropEntries(droppedEntries []droppedEntry, streams []logproto.Stream) []droppedEntry {
	for _, stream := range streams {
		for _, entry := range stream.Entries {
			droppedEntries = dropEntry(droppedEntries, entry.Timestamp, entry.Line)
		}
	}

	return droppedEntries
}
<|endoftext|>


<commit_before>package search

import (
	"math"
	"regexp"
	"sort"

	speeldoos "github.com/thijzert/speeldoos/pkg"
)

var containsRegexChars *regexp.Regexp

func init() {
	containsRegexChars = regexp.MustCompile("[\\(\\)\\{\\}\\\\\\[\\]\\|\\.]")
}

type resulterer interface {
	GetResult(perf speeldoos.Performance) Result
}

type Query struct {
	MinimalRelevance float64
	rootMatcher      resulterer
}

func Compile(q string) (Query, error) {
	return defaultConfig.Compile(q)
}

func (q Query) Search(lib *speeldoos.Library) []Result {
	var rv resultList

	carriers := lib.AllCarriers()
	for _, carrier := range carriers {
		for _, perf := range carrier.Carrier.Performances {
			res := q.rootMatcher.GetResult(perf)

			if res.Relevance.Relevance() >= q.MinimalRelevance {
				rv.Results = append(rv.Results, res)
			}
		}
	}

	sort.Sort(&rv)

	return rv.Results
}

type matcherNode struct {
	f StringMatcher
}

func (n matcherNode) GetResult(perf speeldoos.Performance) Result {
	var rv Result

	rv.Work = n.getWork(perf.Work)
	rv.Performances = append(rv.Performances, n.getPerformance(perf))

	// Find some combined relevance from the work's and performances'. The match
	// should be the highest we find, and the effective relevance should be the
	// sum of the work's relevance plus all the performances' relevance.

	totalRelevance := rv.Work.Relevance.Relevance()
	rv.Relevance.Match = rv.Work.Relevance.Match

	for _, pf := range rv.Performances {
		if rv.Relevance.Match < pf.Relevance.Match {
			rv.Relevance.Match = pf.Relevance.Match
		}
		totalRelevance += pf.Relevance.Relevance()
	}

	if rv.Relevance.Match > 0 {
		rv.Relevance.Significance = totalRelevance / rv.Relevance.Match
	}

	return rv
}

func (n matcherNode) getWork(w speeldoos.Work) work {
	var rv work
	rv.Relevance.Significance = 1.0 // TODO

	rv.Composer.ID = w.Composer.ID
	rv.Composer.Name = n.f.MatchString(w.Composer.Name)
	if !rv.Composer.Name.IsEmpty() {
		rv.Relevance.Match = 1.0 // TODO
	}

	for _, title := range w.Title {
		mt := n.f.MatchString(title.Title)
		if !mt.IsEmpty() {
			rv.Relevance.Match = 1.0 // TODO
		}
		rv.Title = append(rv.Title, mt)
	}

	for _, op := range w.OpusNumber {
		mop := n.f.MatchString(op.String())
		if !mop.IsEmpty() {
			rv.Relevance.Match = 1.0 // TODO
		}
		rv.OpusNumber = append(rv.OpusNumber, mop)
	}

	for _, pt := range w.Parts {
		mpt := part{
			Number: pt.Number,
			Part:   n.f.MatchString(pt.Part),
		}
		if !mpt.Part.IsEmpty() {
			rv.Relevance.Match = 1.0 // TODO
		}
		rv.Parts = append(rv.Parts, mpt)
	}

	rv.Year = w.Year

	return rv
}

func (n matcherNode) getPerformance(p speeldoos.Performance) performance {
	var rv performance
	rv.Relevance.Significance = 1.0 // TODO

	rv.CarrierID = MatchedString{base: "FIXME"} // Carrier ID needs to be passed in with the speeldoos.Performance somehow
	rv.Year = p.Year

	for _, pf := range p.Performers {
		mpf := performer{
			Name: n.f.MatchString(pf.Name),
			Role: pf.Role,
		}
		if !mpf.Name.IsEmpty() {
			rv.Relevance.Match = 1.0 // TODO
		}
		rv.Performers = append(rv.Performers, mpf)
	}

	return rv
}

type andNode struct {
	Parts []resulterer
}

func mergeResults(a, b Result) Result {
	c := Result{
		Relevance: a.Relevance,
		Work: work{
			Composer: a.Work.Composer,
			Year:     a.Work.Year,
		},
	}

	c.Work.Composer.Name = a.Work.Composer.Name.mustCombine(b.Work.Composer.Name)

	for i, title := range a.Work.Title {
		if i < len(b.Work.Title) {
			title = title.mustCombine(b.Work.Title[i])
		}
		c.Work.Title = append(c.Work.Title, title)
	}

	for i, opus := range a.Work.OpusNumber {
		if i < len(b.Work.OpusNumber) {
			opus = opus.mustCombine(b.Work.OpusNumber[i])
		}
		c.Work.OpusNumber = append(c.Work.OpusNumber, opus)
	}

	for i, part := range a.Work.Parts {
		if i < len(b.Work.Parts) {
			part.Part = part.Part.mustCombine(b.Work.Parts[i].Part)
		}
		c.Work.Parts = append(c.Work.Parts, part)
	}

	for j, perf := range a.Performances {
		if j >= len(b.Performances) {
			c.Performances = append(c.Performances, perf)
			continue
		}

		bperf := b.Performances[j]

		mperf := performance{
			CarrierID: perf.CarrierID.mustCombine(bperf.CarrierID),
			Year:      perf.Year,
		}

		for i, prfm := range perf.Performers {
			if i < len(bperf.Performers) {
				prfm.Name = prfm.Name.mustCombine(bperf.Performers[i].Name)
			}
			mperf.Performers = append(mperf.Performers, prfm)
		}

		c.Performances = append(c.Performances, mperf)
	}

	return c
}

func (n andNode) GetResult(perf speeldoos.Performance) Result {
	var rv Result

	var total Relevance

	for i, part := range n.Parts {
		pr := part.GetResult(perf)
		if i == 0 {
			rv = pr
			total.Match = pr.Relevance.Match * pr.Relevance.Match
			total.Significance = pr.Relevance.Relevance()
		} else {
			rv = mergeResults(rv, pr)
			total.Match += pr.Relevance.Match * pr.Relevance.Match
			total.Significance += pr.Relevance.Relevance()
		}
	}

	if len(n.Parts) > 0 {
		total.Match /= float64(len(n.Parts))
	}
	if total.Match > 0 {
		total.Match = math.Sqrt(total.Match)
		total.Significance /= math.Sqrt(total.Match)
	}

	rv.Relevance = total
	return rv
}

func And(a Query, bs ...Query) Query {
	rm := andNode{
		Parts: []resulterer{a.rootMatcher},
	}
	for _, b := range bs {
		rm.Parts = append(rm.Parts, b.rootMatcher)
	}

	return Query{
		MinimalRelevance: a.MinimalRelevance,
		rootMatcher:      rm,
	}
}

type orNode struct {
	Parts []resulterer
}

func (n orNode) GetResult(perf speeldoos.Performance) Result {
	var rv Result

	var total Relevance

	for i, part := range n.Parts {
		pr := part.GetResult(perf)
		if i == 0 {
			rv = pr
			total.Match = pr.Relevance.Match
			total.Significance = pr.Relevance.Relevance()
		} else {
			rv = mergeResults(rv, pr)
			if pr.Relevance.Match > total.Match {
				total.Match = pr.Relevance.Match
			}
			total.Significance += pr.Relevance.Relevance()
		}
	}

	if total.Match > 0 {
		total.Significance /= total.Match
	}
	rv.Relevance = total

	return rv
}

func Or(a Query, bs ...Query) Query {
	rm := orNode{
		Parts: []resulterer{a.rootMatcher},
	}
	for _, b := range bs {
		rm.Parts = append(rm.Parts, b.rootMatcher)
	}

	return Query{
		MinimalRelevance: a.MinimalRelevance,
		rootMatcher:      rm,
	}
}
<commit_msg>If the query has zero minimal relevance, at least filter results that don't match at all<commit_after>package search

import (
	"math"
	"regexp"
	"sort"

	speeldoos "github.com/thijzert/speeldoos/pkg"
)

var containsRegexChars *regexp.Regexp

func init() {
	containsRegexChars = regexp.MustCompile("[\\(\\)\\{\\}\\\\\\[\\]\\|\\.]")
}

type resulterer interface {
	GetResult(perf speeldoos.Performance) Result
}

type Query struct {
	MinimalRelevance float64
	rootMatcher      resulterer
}

func Compile(q string) (Query, error) {
	return defaultConfig.Compile(q)
}

func (q Query) Search(lib *speeldoos.Library) []Result {
	var rv resultList

	carriers := lib.AllCarriers()
	for _, carrier := range carriers {
		for _, perf := range carrier.Carrier.Performances {
			res := q.rootMatcher.GetResult(perf)

			if res.Relevance.Match > 0 && res.Relevance.Relevance() >= q.MinimalRelevance {
				rv.Results = append(rv.Results, res)
			}
		}
	}

	sort.Sort(&rv)

	return rv.Results
}

type matcherNode struct {
	f StringMatcher
}

func (n matcherNode) GetResult(perf speeldoos.Performance) Result {
	var rv Result

	rv.Work = n.getWork(perf.Work)
	rv.Performances = append(rv.Performances, n.getPerformance(perf))

	// Find some combined relevance from the work's and performances'. The match
	// should be the highest we find, and the effective relevance should be the
	// sum of the work's relevance plus all the performances' relevance.

	totalRelevance := rv.Work.Relevance.Relevance()
	rv.Relevance.Match = rv.Work.Relevance.Match

	for _, pf := range rv.Performances {
		if rv.Relevance.Match < pf.Relevance.Match {
			rv.Relevance.Match = pf.Relevance.Match
		}
		totalRelevance += pf.Relevance.Relevance()
	}

	if rv.Relevance.Match > 0 {
		rv.Relevance.Significance = totalRelevance / rv.Relevance.Match
	}

	return rv
}

func (n matcherNode) getWork(w speeldoos.Work) work {
	var rv work
	rv.Relevance.Significance = 1.0 // TODO

	rv.Composer.ID = w.Composer.ID
	rv.Composer.Name = n.f.MatchString(w.Composer.Name)
	if !rv.Composer.Name.IsEmpty() {
		rv.Relevance.Match = 1.0 // TODO
	}

	for _, title := range w.Title {
		mt := n.f.MatchString(title.Title)
		if !mt.IsEmpty() {
			rv.Relevance.Match = 1.0 // TODO
		}
		rv.Title = append(rv.Title, mt)
	}

	for _, op := range w.OpusNumber {
		mop := n.f.MatchString(op.String())
		if !mop.IsEmpty() {
			rv.Relevance.Match = 1.0 // TODO
		}
		rv.OpusNumber = append(rv.OpusNumber, mop)
	}

	for _, pt := range w.Parts {
		mpt := part{
			Number: pt.Number,
			Part:   n.f.MatchString(pt.Part),
		}
		if !mpt.Part.IsEmpty() {
			rv.Relevance.Match = 1.0 // TODO
		}
		rv.Parts = append(rv.Parts, mpt)
	}

	rv.Year = w.Year

	return rv
}

func (n matcherNode) getPerformance(p speeldoos.Performance) performance {
	var rv performance
	rv.Relevance.Significance = 1.0 // TODO

	rv.CarrierID = MatchedString{base: "FIXME"} // Carrier ID needs to be passed in with the speeldoos.Performance somehow
	rv.Year = p.Year

	for _, pf := range p.Performers {
		mpf := performer{
			Name: n.f.MatchString(pf.Name),
			Role: pf.Role,
		}
		if !mpf.Name.IsEmpty() {
			rv.Relevance.Match = 1.0 // TODO
		}
		rv.Performers = append(rv.Performers, mpf)
	}

	return rv
}

type andNode struct {
	Parts []resulterer
}

func mergeResults(a, b Result) Result {
	c := Result{
		Relevance: a.Relevance,
		Work: work{
			Composer: a.Work.Composer,
			Year:     a.Work.Year,
		},
	}

	c.Work.Composer.Name = a.Work.Composer.Name.mustCombine(b.Work.Composer.Name)

	for i, title := range a.Work.Title {
		if i < len(b.Work.Title) {
			title = title.mustCombine(b.Work.Title[i])
		}
		c.Work.Title = append(c.Work.Title, title)
	}

	for i, opus := range a.Work.OpusNumber {
		if i < len(b.Work.OpusNumber) {
			opus = opus.mustCombine(b.Work.OpusNumber[i])
		}
		c.Work.OpusNumber = append(c.Work.OpusNumber, opus)
	}

	for i, part := range a.Work.Parts {
		if i < len(b.Work.Parts) {
			part.Part = part.Part.mustCombine(b.Work.Parts[i].Part)
		}
		c.Work.Parts = append(c.Work.Parts, part)
	}

	for j, perf := range a.Performances {
		if j >= len(b.Performances) {
			c.Performances = append(c.Performances, perf)
			continue
		}

		bperf := b.Performances[j]

		mperf := performance{
			CarrierID: perf.CarrierID.mustCombine(bperf.CarrierID),
			Year:      perf.Year,
		}

		for i, prfm := range perf.Performers {
			if i < len(bperf.Performers) {
				prfm.Name = prfm.Name.mustCombine(bperf.Performers[i].Name)
			}
			mperf.Performers = append(mperf.Performers, prfm)
		}

		c.Performances = append(c.Performances, mperf)
	}

	return c
}

func (n andNode) GetResult(perf speeldoos.Performance) Result {
	var rv Result

	var total Relevance

	for i, part := range n.Parts {
		pr := part.GetResult(perf)
		if i == 0 {
			rv = pr
			total.Match = pr.Relevance.Match * pr.Relevance.Match
			total.Significance = pr.Relevance.Relevance()
		} else {
			rv = mergeResults(rv, pr)
			total.Match += pr.Relevance.Match * pr.Relevance.Match
			total.Significance += pr.Relevance.Relevance()
		}
	}

	if len(n.Parts) > 0 {
		total.Match /= float64(len(n.Parts))
	}
	if total.Match > 0 {
		total.Match = math.Sqrt(total.Match)
		total.Significance /= math.Sqrt(total.Match)
	}

	rv.Relevance = total
	return rv
}

func And(a Query, bs ...Query) Query {
	rm := andNode{
		Parts: []resulterer{a.rootMatcher},
	}
	for _, b := range bs {
		rm.Parts = append(rm.Parts, b.rootMatcher)
	}

	return Query{
		MinimalRelevance: a.MinimalRelevance,
		rootMatcher:      rm,
	}
}

type orNode struct {
	Parts []resulterer
}

func (n orNode) GetResult(perf speeldoos.Performance) Result {
	var rv Result

	var total Relevance

	for i, part := range n.Parts {
		pr := part.GetResult(perf)
		if i == 0 {
			rv = pr
			total.Match = pr.Relevance.Match
			total.Significance = pr.Relevance.Relevance()
		} else {
			rv = mergeResults(rv, pr)
			if pr.Relevance.Match > total.Match {
				total.Match = pr.Relevance.Match
			}
			total.Significance += pr.Relevance.Relevance()
		}
	}

	if total.Match > 0 {
		total.Significance /= total.Match
	}
	rv.Relevance = total

	return rv
}

func Or(a Query, bs ...Query) Query {
	rm := orNode{
		Parts: []resulterer{a.rootMatcher},
	}
	for _, b := range bs {
		rm.Parts = append(rm.Parts, b.rootMatcher)
	}

	return Query{
		MinimalRelevance: a.MinimalRelevance,
		rootMatcher:      rm,
	}
}
<|endoftext|>


<commit_before>package snap

import (
	"fmt"
	"os"
	"path"
	"time"

	"github.com/intelsdi-x/athena/pkg/conf"
	snapProcessorTag "github.com/intelsdi-x/snap-plugin-processor-tag/tag"
	"github.com/intelsdi-x/snap/mgmt/rest/client"
	"github.com/intelsdi-x/snap/scheduler/wmap"
	"github.com/pkg/errors"
)

// SnapteldHTTPEndpoint represents snap daemon address flag.
var SnapteldHTTPEndpoint = conf.NewStringFlag("snapteld_addr", "Snapteld HTTP Endpoint", "http://127.0.0.1:8181")

type task struct {
	Version  int
	Schedule *client.Schedule
	Workflow *wmap.WorkflowMap
	Name     string
	Deadline string
	ID       string
	State    string
}

// CollectNodeConfigItem represents ConfigItem in CollectWorkflowMapNode.
type CollectNodeConfigItem struct {
	Ns    string
	Key   string
	Value interface{}
}

// Session provides construct for tagging metrics for a specified time span
// defined by Start() and Stop().
type Session struct {
	// Schedule defines the schedule type and interval for the listed metrics.
	Schedule *client.Schedule

	// Metrics to tag in session.
	Metrics []string

	// CollectNodeConfigItems represent ConfigItems for CollectNode.
	CollectNodeConfigItems []CollectNodeConfigItem

	// Active task.
	task *task

	// Publisher for tagged metrics.
	Publisher *wmap.PublishWorkflowMapNode

	// Client to Snapteld.
	pClient *client.Client
}

// NewSession generates a session with a name and a list of metrics to tag.
// The interval cannot be less than second granularity.
func NewSession(
	metrics []string,
	interval time.Duration,
	pClient *client.Client,
	publisher *wmap.PublishWorkflowMapNode) *Session {

	// Convert from duration to "Xs" string.
	secondString := fmt.Sprintf("%ds", int(interval.Seconds()))

	return &Session{
		Schedule: &client.Schedule{
			Type:     "simple",
			Interval: secondString,
		},
		Metrics:                metrics,
		pClient:                pClient,
		Publisher:              publisher, // TODO(niklas): Replace with cassandra publisher.
		CollectNodeConfigItems: []CollectNodeConfigItem{},
	}
}

// Start an experiment session.
func (s *Session) Start(tags string) error {
	if s.task != nil {
		return errors.New("task already running")
	}

	t := &task{
		Version:  1,
		Schedule: s.Schedule,
	}

	wf := wmap.NewWorkflowMap()

	for _, metric := range s.Metrics {
		wf.CollectNode.AddMetric(metric, -1)
	}

	for _, configItem := range s.CollectNodeConfigItems {
		wf.CollectNode.AddConfigItem(configItem.Ns, configItem.Key, configItem.Value)
	}

	// Check if `processor-tag` plugins is loaded.
	// Get the Name of the processor-tag plugin from its Meta.
	plugins := NewPlugins(s.pClient)
	loaded, err := plugins.IsLoaded("processor", snapProcessorTag.Meta().Name)
	if err != nil {
		return err
	}

	if !loaded {
		goPath := os.Getenv("GOPATH")
		pluginPath := path.Join(goPath, "bin", "snap-plugin-processor-tag")
		err = plugins.LoadPlugin(pluginPath)
		if err != nil {
			return err
		}
	}

	pr := wmap.NewProcessNode(snapProcessorTag.Meta().Name, 3)
	pr.AddConfigItem("tags", tags)

	// Add specified publisher to workflow as well.
	pr.Add(s.Publisher)
	wf.CollectNode.Add(pr)

	t.Workflow = wf

	r := s.pClient.CreateTask(t.Schedule, t.Workflow, t.Name, t.Deadline, true, 10)
	if r.Err != nil {
		return errors.Wrapf(r.Err, "could not create task %q", t.Name)
	}

	// Save a copy of the task so we can stop it again.
	t.ID = r.ID
	t.State = r.State
	s.task = t

	return nil
}

// IsRunning checks if Snap task is running.
func (s *Session) IsRunning() bool {
	status, err := s.status()
	if err != nil {
		return false
	}
	return status == "Running"
}

// Status connects to snap to verify the current state of the task.
func (s *Session) status() (string, error) {
	if s.task == nil {
		return "", errors.New("snap task is not running or not found")
	}

	task := s.pClient.GetTask(s.task.ID)
	if task.Err != nil {
		return "", errors.Wrapf(task.Err, "could not get task name:%q, ID:%q",
			s.task.Name, s.task.ID)
	}

	return task.State, nil
}

// Stop terminates an experiment session and removes Snap task.
// This function blocks until task is stopped.
func (s *Session) Stop() error {
	if s.task == nil {
		return errors.New("snap task not running or not found")
	}

	rs := s.pClient.StopTask(s.task.ID)
	if rs.Err != nil {
		return errors.Wrapf(rs.Err, "could not send stop signal to task %q", s.task.ID)
	}

	err := s.waitForStop()
	if err != nil {
		return errors.Wrapf(err, "could not stop task %q", s.task.ID)
	}

	rr := s.pClient.RemoveTask(s.task.ID)
	if rr.Err != nil {
		return errors.Wrapf(rr.Err, "could not remove task %q", s.task.ID)
	}

	s.task = nil

	return nil
}

// Wait blocks until the task is executed at least once
// (including hits that happened in the past).
func (s *Session) Wait() error {
	for {
		t := s.pClient.GetTask(s.task.ID)
		if t.Err != nil {
			return errors.Wrapf(t.Err, "getting task %q failed", s.task.ID)
		}

		if t.State == "Stopped" || t.State == "Disabled" {
			return errors.Errorf("failed to wait for task: task %q is in state %q (last failure: %q)",
				s.task.ID,
				t.State,
				t.LastFailureMessage)

		}

		if (t.HitCount - (t.FailedCount + t.MissCount)) > 0 {
			return nil
		}
		time.Sleep(100 * time.Millisecond)
	}
}

func (s *Session) waitForStop() error {
	for {
		t := s.pClient.GetTask(s.task.ID)
		if t.Err != nil {
			return errors.Wrapf(t.Err, "could not get task %q", s.task.ID)
		}

		if t.State == "Stopped" || t.State == "Disabled" {
			return nil
		}

		time.Sleep(100 * time.Millisecond)
	}
}
<commit_msg>Simplify plugin loading (#67)<commit_after>package snap

import (
	"fmt"
	"time"

	"github.com/intelsdi-x/athena/pkg/conf"
	snapProcessorTag "github.com/intelsdi-x/snap-plugin-processor-tag/tag"
	"github.com/intelsdi-x/snap/mgmt/rest/client"
	"github.com/intelsdi-x/snap/scheduler/wmap"
	"github.com/pkg/errors"
)

// SnapteldHTTPEndpoint represents snap daemon address flag.
var SnapteldHTTPEndpoint = conf.NewStringFlag("snapteld_addr", "Snapteld HTTP Endpoint", "http://127.0.0.1:8181")

type task struct {
	Version  int
	Schedule *client.Schedule
	Workflow *wmap.WorkflowMap
	Name     string
	Deadline string
	ID       string
	State    string
}

// CollectNodeConfigItem represents ConfigItem in CollectWorkflowMapNode.
type CollectNodeConfigItem struct {
	Ns    string
	Key   string
	Value interface{}
}

// Session provides construct for tagging metrics for a specified time span
// defined by Start() and Stop().
type Session struct {
	// Schedule defines the schedule type and interval for the listed metrics.
	Schedule *client.Schedule

	// Metrics to tag in session.
	Metrics []string

	// CollectNodeConfigItems represent ConfigItems for CollectNode.
	CollectNodeConfigItems []CollectNodeConfigItem

	// Active task.
	task *task

	// Publisher for tagged metrics.
	Publisher *wmap.PublishWorkflowMapNode

	// Client to Snapteld.
	pClient *client.Client
}

// NewSession generates a session with a name and a list of metrics to tag.
// The interval cannot be less than second granularity.
func NewSession(
	metrics []string,
	interval time.Duration,
	pClient *client.Client,
	publisher *wmap.PublishWorkflowMapNode) *Session {

	// Convert from duration to "Xs" string.
	secondString := fmt.Sprintf("%ds", int(interval.Seconds()))

	return &Session{
		Schedule: &client.Schedule{
			Type:     "simple",
			Interval: secondString,
		},
		Metrics:                metrics,
		pClient:                pClient,
		Publisher:              publisher, // TODO(niklas): Replace with cassandra publisher.
		CollectNodeConfigItems: []CollectNodeConfigItem{},
	}
}

// Start an experiment session.
func (s *Session) Start(tags string) error {
	if s.task != nil {
		return errors.New("task already running")
	}

	t := &task{
		Version:  1,
		Schedule: s.Schedule,
	}

	wf := wmap.NewWorkflowMap()

	for _, metric := range s.Metrics {
		wf.CollectNode.AddMetric(metric, -1)
	}

	for _, configItem := range s.CollectNodeConfigItems {
		wf.CollectNode.AddConfigItem(configItem.Ns, configItem.Key, configItem.Value)
	}

	loaderConfig := DefaultPluginLoaderConfig()
	loaderConfig.SnapteldAddress = s.pClient.URL
	loader, err := NewPluginLoader(loaderConfig)
	if err != nil {
		return err
	}

	if err := loader.Load(TagProcessor); err != nil {
		return err
	}

	pr := wmap.NewProcessNode(snapProcessorTag.Meta().Name, 3)
	pr.AddConfigItem("tags", tags)

	// Add specified publisher to workflow as well.
	pr.Add(s.Publisher)
	wf.CollectNode.Add(pr)

	t.Workflow = wf

	r := s.pClient.CreateTask(t.Schedule, t.Workflow, t.Name, t.Deadline, true, 10)
	if r.Err != nil {
		return errors.Wrapf(r.Err, "could not create task %q", t.Name)
	}

	// Save a copy of the task so we can stop it again.
	t.ID = r.ID
	t.State = r.State
	s.task = t

	return nil
}

// IsRunning checks if Snap task is running.
func (s *Session) IsRunning() bool {
	status, err := s.status()
	if err != nil {
		return false
	}
	return status == "Running"
}

// Status connects to snap to verify the current state of the task.
func (s *Session) status() (string, error) {
	if s.task == nil {
		return "", errors.New("snap task is not running or not found")
	}

	task := s.pClient.GetTask(s.task.ID)
	if task.Err != nil {
		return "", errors.Wrapf(task.Err, "could not get task name:%q, ID:%q",
			s.task.Name, s.task.ID)
	}

	return task.State, nil
}

// Stop terminates an experiment session and removes Snap task.
// This function blocks until task is stopped.
func (s *Session) Stop() error {
	if s.task == nil {
		return errors.New("snap task not running or not found")
	}

	rs := s.pClient.StopTask(s.task.ID)
	if rs.Err != nil {
		return errors.Wrapf(rs.Err, "could not send stop signal to task %q", s.task.ID)
	}

	err := s.waitForStop()
	if err != nil {
		return errors.Wrapf(err, "could not stop task %q", s.task.ID)
	}

	rr := s.pClient.RemoveTask(s.task.ID)
	if rr.Err != nil {
		return errors.Wrapf(rr.Err, "could not remove task %q", s.task.ID)
	}

	s.task = nil

	return nil
}

// Wait blocks until the task is executed at least once
// (including hits that happened in the past).
func (s *Session) Wait() error {
	for {
		t := s.pClient.GetTask(s.task.ID)
		if t.Err != nil {
			return errors.Wrapf(t.Err, "getting task %q failed", s.task.ID)
		}

		if t.State == "Stopped" || t.State == "Disabled" {
			return errors.Errorf("failed to wait for task: task %q is in state %q (last failure: %q)",
				s.task.ID,
				t.State,
				t.LastFailureMessage)

		}

		if (t.HitCount - (t.FailedCount + t.MissCount)) > 0 {
			return nil
		}
		time.Sleep(100 * time.Millisecond)
	}
}

func (s *Session) waitForStop() error {
	for {
		t := s.pClient.GetTask(s.task.ID)
		if t.Err != nil {
			return errors.Wrapf(t.Err, "could not get task %q", s.task.ID)
		}

		if t.State == "Stopped" || t.State == "Disabled" {
			return nil
		}

		time.Sleep(100 * time.Millisecond)
	}
}
<|endoftext|>