<commit_before>package main

import (
	"bufio"
	"compress/gzip"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"regexp"
	"strings"
	"time"
	"unicode/utf8"
)

const delimiter string = "::::"

var runecount = utf8.RuneCountInString

func fail(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
func failOn(b bool, message string) {
	if b {
		fail(errors.New(message))
	}
}

type Stack struct {
	s   []string
	dir string
}

func NewStack(capacity int) *Stack {
	s := make([]string, 0, capacity)
	return &Stack{s, ""}
}

func (s *Stack) Push(dirname string) {
	s.s = append(s.s, dirname)
	s.dir = filepath.Join(s.s...)
}

func (s *Stack) Pop() {
	failOn(len(s.s) <= 0, "Invalid directory state. Corrupted database?")
	i := len(s.s) - 1
	s.s = s.s[:i]
	s.dir = filepath.Join(s.s...)
}

func (s Stack) Dir() string {
	return s.dir
}

func keyval(line string) (string, string) {
	i := strings.Index(line, ":")
	if i == -1 || i == len(line)-1 {
		return line, ""
	}
	return line[:i], line[i+2:]
}

type Track struct {
	Album    string
	Artist   string
	Date     string
	Filename string
	Genre    string
	Path     string
	Time     string
	Title    string
}

func (t *Track) Set(key, value string) {
	switch key {
	case "Album":
		t.Album = value
	case "Artist":
		t.Artist = value
	case "Date":
		t.Date = value
	case "Genre":
		t.Genre = value
	case "Time":
		t.Time = formatDurationString(value)
	case "Title":
		t.Title = value
	}
}

func formatDurationString(str string) string {
	duration, err := time.ParseDuration(str + "s")
	if err != nil {
		return ""
	}
	zero := time.Time{}
	format := zero.Add(duration).Format("04:05")
	if duration > time.Hour {
		format = fmt.Sprintf("%d:%s", int(duration.Hours()), format)
	}
	return "(" + format + ")"
}

func spaceBetween(left, right string, maxchars int) string {
	n := maxchars - runecount(left) - runecount(right)
	return left + strings.Repeat(" ", n) + right
}

func withoutExt(path string) string {
	basename := filepath.Base(path)
	return strings.TrimSuffix(basename, filepath.Ext(basename))
}

func truncate(s string, max int, suffix string) string {
	max -= runecount(suffix)
	if max < 0 {
		panic("suffix length greater than max chars")
	}
	trunc := false
	count := 0
	out := make([]rune, 0, max)
	for _, r := range s {
		if count >= max {
			trunc = true
			break
		}
		out = append(out, r)
		count += 1
	}
	result := string(out)
	if trunc {
		result += suffix
	}
	return result
}

func trackFormatter() func(*Track) string {
	cmd := exec.Command("stty", "size")
	cmd.Stdin = os.Stdin
	out, err := cmd.Output()
	fail(err)
	var height, width int
	_, err = fmt.Sscanf(string(out), "%d %d\n", &height, &width)
	fail(err)
	contentLen := width - 5 // remove 5 for fzf display
	return func(t *Track) string {
		str := t.Artist + " - " + t.Title
		str = strings.TrimPrefix(str, " - ")
		if str == "" {
			str = withoutExt(t.Filename)
		}
		if t.Album != "" {
			str += " {" + t.Album + "}"
		}
		str = truncate(str, contentLen-len(t.Time), "..")
		str = spaceBetween(str, t.Time, contentLen)
		str = strings.Replace(str, delimiter, "", -1)
		return str + delimiter + t.Path
	}
}

func groupByArtist(tracks []*Track) []*Track {
	// group by artist, then shuffle to stop same order, but keep artist together
	artists := map[string][]*Track{}
	for _, t := range tracks {
		artists[t.Artist] = append(artists[t.Artist], t)
	}
	shuffled := make([]*Track, len(tracks))
	i := 0
	for _, tracks := range artists {
		for _, t := range tracks {
			shuffled[i] = t
			i += 1
		}
	}
	return shuffled
}

func parse(scan *bufio.Scanner) []*Track {

	tracks, track := []*Track{}, new(Track)
	dirstack := NewStack(256)

	for scan.Scan() {
		key, value := keyval(scan.Text())
		switch key {
		case "directory":
			dirstack.Push(value)
		case "end":
			dirstack.Pop()
		case "Artist", "Album", "Date", "Genre", "Time", "Title":
			track.Set(key, value)
		case "song_begin":
			track.Filename = value
			track.Path = filepath.Join(dirstack.Dir(), track.Filename)
		case "song_end":
			tracks = append(tracks, track)
			track = new(Track)
		}
	}
	fail(scan.Err())
	return tracks
}

func expandUser(path, home string) string {
	if path[:2] == "~/" {
		path = strings.Replace(path, "~", home, 1)
	}
	return path
}

func findDbFile() string {
	usr, err := user.Current()
	fail(err)
	home := usr.HomeDir
	paths := []string{
		filepath.Join(os.Getenv("XDG_CONFIG_HOME"), "/mpd/mpd.conf"),
		filepath.Join(home, ".config", "/mpd/mpd.conf"),
		filepath.Join(home, ".mpdconf"),
		"/etc/mpd.conf",
	}
	var f *os.File
	var confpath string
	for _, path := range paths {
		f, err = os.Open(path)
		if err == nil {
			confpath = path
			break
		}
	}
	failOn(f == nil, "No config file found")

	expDb := regexp.MustCompile(`^\s*db_file\s*"([^"]+)"`)
	scan := bufio.NewScanner(f)
	var dbFile string
	for scan.Scan() {
		m := expDb.FindStringSubmatch(scan.Text())
		if m != nil {
			dbFile = expandUser(m[1], home)
		}
	}
	fail(scan.Err())
	fail(f.Close())
	failOn(dbFile == "", fmt.Sprintf("Could not find 'db_file' in configuration file '%s'", confpath))
	return dbFile
}

func fzfcmd() *exec.Cmd {
	bind := "--bind=enter:execute-silent(mpd-fzf-play {})"
	fzf := exec.Command("fzf", "--no-hscroll", bind)
	fzf.Stderr = os.Stderr
	return fzf
}

func main() {
	dbFile := findDbFile()
	format := trackFormatter()

	f, err := os.Open(dbFile)
	fail(err)
	gz, err := gzip.NewReader(f)
	fail(err)

	scan := bufio.NewScanner(gz)
	tracks := groupByArtist(parse(scan))

	fail(gz.Close())
	fail(f.Close())

	fzf := fzfcmd()
	in, _ := fzf.StdinPipe()
	fail(fzf.Start())
	for _, t := range tracks {
		fmt.Fprintln(in, format(t))
	}
	fail(in.Close())
	fzf.Wait()
}
<commit_msg>Simplify stack<commit_after>package main

import (
	"bufio"
	"compress/gzip"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"regexp"
	"strings"
	"time"
	"unicode/utf8"
)

const delimiter string = "::::"

var runecount = utf8.RuneCountInString

func fail(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
func failOn(b bool, message string) {
	if b {
		fail(errors.New(message))
	}
}

type Stack []string

func (s *Stack) Push(dirname string) {
	*s = append(*s, dirname)
}

func (s *Stack) DiscardTop() {
	stack := *s
	failOn(len(stack) <= 0, "Invalid directory state. Corrupted database?")
	i := len(stack) - 1
	*s = stack[:i]
}

func keyval(line string) (string, string) {
	i := strings.Index(line, ":")
	if i == -1 || i == len(line)-1 {
		return line, ""
	}
	return line[:i], line[i+2:]
}

type Track struct {
	Album    string
	Artist   string
	Date     string
	Filename string
	Genre    string
	Path     string
	Time     string
	Title    string
}

func (t *Track) Set(key, value string) {
	switch key {
	case "Album":
		t.Album = value
	case "Artist":
		t.Artist = value
	case "Date":
		t.Date = value
	case "Genre":
		t.Genre = value
	case "Time":
		t.Time = formatDurationString(value)
	case "Title":
		t.Title = value
	}
}

func formatDurationString(str string) string {
	duration, err := time.ParseDuration(str + "s")
	if err != nil {
		return ""
	}
	zero := time.Time{}
	format := zero.Add(duration).Format("04:05")
	if duration > time.Hour {
		format = fmt.Sprintf("%d:%s", int(duration.Hours()), format)
	}
	return "(" + format + ")"
}

func spaceBetween(left, right string, maxchars int) string {
	n := maxchars - runecount(left) - runecount(right)
	return left + strings.Repeat(" ", n) + right
}

func withoutExt(path string) string {
	basename := filepath.Base(path)
	return strings.TrimSuffix(basename, filepath.Ext(basename))
}

func truncate(s string, max int, suffix string) string {
	max -= runecount(suffix)
	if max < 0 {
		panic("suffix length greater than max chars")
	}
	trunc := false
	count := 0
	out := make([]rune, 0, max)
	for _, r := range s {
		if count >= max {
			trunc = true
			break
		}
		out = append(out, r)
		count += 1
	}
	result := string(out)
	if trunc {
		result += suffix
	}
	return result
}

func trackFormatter() func(*Track) string {
	cmd := exec.Command("stty", "size")
	cmd.Stdin = os.Stdin
	out, err := cmd.Output()
	fail(err)
	var height, width int
	_, err = fmt.Sscanf(string(out), "%d %d\n", &height, &width)
	fail(err)
	contentLen := width - 5 // remove 5 for fzf display
	return func(t *Track) string {
		str := t.Artist + " - " + t.Title
		str = strings.TrimPrefix(str, " - ")
		if str == "" {
			str = withoutExt(t.Filename)
		}
		if t.Album != "" {
			str += " {" + t.Album + "}"
		}
		str = truncate(str, contentLen-len(t.Time), "..")
		str = spaceBetween(str, t.Time, contentLen)
		str = strings.Replace(str, delimiter, "", -1)
		return str + delimiter + t.Path
	}
}

func groupByArtist(tracks []*Track) []*Track {
	// group by artist, then shuffle to stop same order, but keep artist together
	artists := map[string][]*Track{}
	for _, t := range tracks {
		artists[t.Artist] = append(artists[t.Artist], t)
	}
	shuffled := make([]*Track, len(tracks))
	i := 0
	for _, tracks := range artists {
		for _, t := range tracks {
			shuffled[i] = t
			i += 1
		}
	}
	return shuffled
}

func parse(scan *bufio.Scanner) []*Track {

	tracks, track := []*Track{}, new(Track)
	dirstack := make(Stack, 0, 64)

	for scan.Scan() {
		key, value := keyval(scan.Text())
		switch key {
		case "directory":
			dirstack.Push(value)
		case "end":
			dirstack.DiscardTop()
		case "Artist", "Album", "Date", "Genre", "Time", "Title":
			track.Set(key, value)
		case "song_begin":
			track.Filename = value
			track.Path = filepath.Join(append(dirstack, track.Filename)...)
		case "song_end":
			tracks = append(tracks, track)
			track = new(Track)
		}
	}
	fail(scan.Err())
	return tracks
}

func expandUser(path, home string) string {
	if path[:2] == "~/" {
		path = strings.Replace(path, "~", home, 1)
	}
	return path
}

func findDbFile() string {
	usr, err := user.Current()
	fail(err)
	home := usr.HomeDir
	paths := []string{
		filepath.Join(os.Getenv("XDG_CONFIG_HOME"), "/mpd/mpd.conf"),
		filepath.Join(home, ".config", "/mpd/mpd.conf"),
		filepath.Join(home, ".mpdconf"),
		"/etc/mpd.conf",
	}
	var f *os.File
	var confpath string
	for _, path := range paths {
		f, err = os.Open(path)
		if err == nil {
			confpath = path
			break
		}
	}
	failOn(f == nil, "No config file found")

	expDb := regexp.MustCompile(`^\s*db_file\s*"([^"]+)"`)
	scan := bufio.NewScanner(f)
	var dbFile string
	for scan.Scan() {
		m := expDb.FindStringSubmatch(scan.Text())
		if m != nil {
			dbFile = expandUser(m[1], home)
		}
	}
	fail(scan.Err())
	fail(f.Close())
	failOn(dbFile == "", fmt.Sprintf("Could not find 'db_file' in configuration file '%s'", confpath))
	return dbFile
}

func fzfcmd() *exec.Cmd {
	bind := "--bind=enter:execute-silent(mpd-fzf-play {})"
	fzf := exec.Command("fzf", "--no-hscroll", bind)
	fzf.Stderr = os.Stderr
	return fzf
}

func main() {
	dbFile := findDbFile()
	format := trackFormatter()

	f, err := os.Open(dbFile)
	fail(err)
	gz, err := gzip.NewReader(f)
	fail(err)

	scan := bufio.NewScanner(gz)
	tracks := groupByArtist(parse(scan))

	fail(gz.Close())
	fail(f.Close())

	fzf := fzfcmd()
	in, _ := fzf.StdinPipe()
	fail(fzf.Start())
	for _, t := range tracks {
		fmt.Fprintln(in, format(t))
	}
	fail(in.Close())
	fzf.Wait()
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Knative 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 v1alpha1_test

import (
	"testing"

	"github.com/knative/build-pipeline/pkg/apis/pipeline/v1alpha1"
	tb "github.com/knative/build-pipeline/test/builder"
)

func TestPipelineSpec_Validate_Error(t *testing.T) {
	tests := []struct {
		name string
		p    *v1alpha1.Pipeline
	}{
		{
			name: "duplicate tasks",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineTask("foo", "foo-task"),
				tb.PipelineTask("foo", "foo-task"),
			)),
		},
		{
			name: "providedby task doesnt exist",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "great-resource", tb.ProvidedBy("bar"))),
			)),
		},
		{
			name: "providedby task is afterward",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "great-resource", tb.ProvidedBy("bar"))),
				tb.PipelineTask("bar", "bar-task",
					tb.PipelineTaskOutputResource("the-resource", "great-resource")),
			)),
		},
		{
			name: "unused resources declared",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineDeclaredResource("extra-resource", v1alpha1.PipelineResourceTypeImage),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "great-resource")),
			)),
		},
		{
			name: "output resources missing from declaration",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "great-resource"),
					tb.PipelineTaskOutputResource("the-magic-resource", "missing-resource")),
			)),
		},
		{
			name: "input resources missing from declaration",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "missing-resource"),
					tb.PipelineTaskOutputResource("the-magic-resource", "great-resource")),
			)),
		},
		{
			name: "providedBy resource isn't output by task",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineDeclaredResource("wonderful-resource", v1alpha1.PipelineResourceTypeImage),
				tb.PipelineTask("bar", "bar-task",
					tb.PipelineTaskInputResource("some-workspace", "great-resource")),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("wow-image", "wonderful-resource", tb.ProvidedBy("bar"))),
			)),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := tt.p.Spec.Validate(); err == nil {
				t.Error("PipelineSpec.Validate() did not return error, wanted error")
			}
		})
	}
}

func TestPipelineSpec_Validate_Valid(t *testing.T) {
	tests := []struct {
		name string
		p    *v1alpha1.Pipeline
	}{
		{
			name: "no duplicate tasks",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineTask("foo", "foo-task"),
				tb.PipelineTask("bar", "bar-task"),
			)),
		},
		{
			// Adding this case because `task.Resources` is a pointer, explicitly making sure this is handled
			name: "task without resources",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("wonderful-resource", v1alpha1.PipelineResourceTypeImage),
				tb.PipelineTask("bar", "bar-task"),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("wow-image", "wonderful-resource")),
			)),
		},
		{
			name: "valid resource declarations and usage",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineDeclaredResource("wonderful-resource", v1alpha1.PipelineResourceTypeImage),
				tb.PipelineTask("bar", "bar-task",
					tb.PipelineTaskInputResource("some-workspace", "great-resource"),
					tb.PipelineTaskOutputResource("some-image", "wonderful-resource")),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("wow-image", "wonderful-resource", tb.ProvidedBy("bar"))),
			)),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := tt.p.Spec.Validate(); err != nil {
				t.Errorf("PipelineSpec.Validate() returned error: %v", err)
			}
		})
	}
}
<commit_msg>Add test coverage for pipeline providedBy<commit_after>/*
Copyright 2018 The Knative 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 v1alpha1_test

import (
	"testing"

	"github.com/knative/build-pipeline/pkg/apis/pipeline/v1alpha1"
	tb "github.com/knative/build-pipeline/test/builder"
)

func TestPipelineSpec_Validate_Error(t *testing.T) {
	tests := []struct {
		name string
		p    *v1alpha1.Pipeline
	}{
		{
			name: "duplicate tasks",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineTask("foo", "foo-task"),
				tb.PipelineTask("foo", "foo-task"),
			)),
		},
		{
			name: "providedby is on first task",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "great-resource", tb.ProvidedBy("bar"))),
			)),
		},
		{
			name: "providedby task doesnt exist",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineTask("baz", "baz-task"),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "great-resource", tb.ProvidedBy("bar"))),
			)),
		},
		{
			name: "providedby task is afterward",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "great-resource", tb.ProvidedBy("bar"))),
				tb.PipelineTask("bar", "bar-task",
					tb.PipelineTaskOutputResource("the-resource", "great-resource")),
			)),
		},
		{
			name: "unused resources declared",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineDeclaredResource("extra-resource", v1alpha1.PipelineResourceTypeImage),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "great-resource")),
			)),
		},
		{
			name: "output resources missing from declaration",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "great-resource"),
					tb.PipelineTaskOutputResource("the-magic-resource", "missing-resource")),
			)),
		},
		{
			name: "input resources missing from declaration",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("the-resource", "missing-resource"),
					tb.PipelineTaskOutputResource("the-magic-resource", "great-resource")),
			)),
		},
		{
			name: "providedBy resource isn't output by task",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineDeclaredResource("wonderful-resource", v1alpha1.PipelineResourceTypeImage),
				tb.PipelineTask("bar", "bar-task",
					tb.PipelineTaskInputResource("some-workspace", "great-resource")),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("wow-image", "wonderful-resource", tb.ProvidedBy("bar"))),
			)),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := tt.p.Spec.Validate(); err == nil {
				t.Error("PipelineSpec.Validate() did not return error, wanted error")
			}
		})
	}
}

func TestPipelineSpec_Validate_Valid(t *testing.T) {
	tests := []struct {
		name string
		p    *v1alpha1.Pipeline
	}{
		{
			name: "no duplicate tasks",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineTask("foo", "foo-task"),
				tb.PipelineTask("bar", "bar-task"),
			)),
		},
		{
			// Adding this case because `task.Resources` is a pointer, explicitly making sure this is handled
			name: "task without resources",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("wonderful-resource", v1alpha1.PipelineResourceTypeImage),
				tb.PipelineTask("bar", "bar-task"),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("wow-image", "wonderful-resource")),
			)),
		},
		{
			name: "valid resource declarations and usage",
			p: tb.Pipeline("pipeline", "namespace", tb.PipelineSpec(
				tb.PipelineDeclaredResource("great-resource", v1alpha1.PipelineResourceTypeGit),
				tb.PipelineDeclaredResource("wonderful-resource", v1alpha1.PipelineResourceTypeImage),
				tb.PipelineTask("bar", "bar-task",
					tb.PipelineTaskInputResource("some-workspace", "great-resource"),
					tb.PipelineTaskOutputResource("some-image", "wonderful-resource")),
				tb.PipelineTask("foo", "foo-task",
					tb.PipelineTaskInputResource("wow-image", "wonderful-resource", tb.ProvidedBy("bar"))),
			)),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := tt.p.Spec.Validate(); err != nil {
				t.Errorf("PipelineSpec.Validate() returned error: %v", err)
			}
		})
	}
}
<|endoftext|>


<commit_before>package prometheus_client

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/internal"
	"github.com/influxdata/telegraf/plugins/outputs"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var invalidNameCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)

// SampleID uniquely identifies a Sample
type SampleID string

// Sample represents the current value of a series.
type Sample struct {
	// Labels are the Prometheus labels.
	Labels map[string]string
	// Value is the value in the Prometheus output. Only one of these will populated.
	Value          float64
	HistogramValue map[float64]uint64
	SummaryValue   map[float64]float64
	// Histograms and Summaries need a count and a sum
	Count uint64
	Sum   float64
	// Expiration is the deadline that this Sample is valid until.
	Expiration time.Time
}

// MetricFamily contains the data required to build valid prometheus Metrics.
type MetricFamily struct {
	// Samples are the Sample belonging to this MetricFamily.
	Samples map[SampleID]*Sample
	// Need the telegraf ValueType because there isn't a Prometheus ValueType
	// representing Histogram or Summary
	TelegrafValueType telegraf.ValueType
	// LabelSet is the label counts for all Samples.
	LabelSet map[string]int
}

type PrometheusClient struct {
	Listen             string
	ExpirationInterval internal.Duration `toml:"expiration_interval"`
	Path               string            `toml:"path"`
	CollectorsExclude  []string          `toml:"collectors_exclude"`

	server *http.Server

	sync.Mutex
	// fam is the non-expired MetricFamily by Prometheus metric name.
	fam map[string]*MetricFamily
	// now returns the current time.
	now func() time.Time
}

var sampleConfig = `
  ## Address to listen on
  # listen = ":9273"

  ## Interval to expire metrics and not deliver to prometheus, 0 == no expiration
  # expiration_interval = "60s"

  ## Collectors to enable, valid entries are "gocollector" and "process".
  ## If unset, both are enabled.
  collectors_exclude = ["gocollector", "process"]
`

func (p *PrometheusClient) Start() error {
	prometheus.Register(p)

	for _, collector := range p.CollectorsExclude {
		switch collector {
		case "gocollector":
			prometheus.Unregister(prometheus.NewGoCollector())
		case "process":
			prometheus.Unregister(prometheus.NewProcessCollector(os.Getpid(), ""))
		default:
			return fmt.Errorf("unrecognized collector %s", collector)
		}
	}

	if p.Listen == "" {
		p.Listen = "localhost:9273"
	}

	if p.Path == "" {
		p.Path = "/metrics"
	}

	mux := http.NewServeMux()
	mux.Handle(p.Path, promhttp.HandlerFor(
		prometheus.DefaultGatherer,
		promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}))

	p.server = &http.Server{
		Addr:    p.Listen,
		Handler: mux,
	}

	go func() {
		if err := p.server.ListenAndServe(); err != nil {
			if err != http.ErrServerClosed {
				log.Printf("E! Error creating prometheus metric endpoint, err: %s\n",
					err.Error())
			}
		}
	}()
	return nil
}

func (p *PrometheusClient) Stop() {
	// plugin gets cleaned up in Close() already.
}

func (p *PrometheusClient) Connect() error {
	// This service output does not need to make any further connections
	return nil
}

func (p *PrometheusClient) Close() error {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()
	err := p.server.Shutdown(ctx)
	prometheus.Unregister(p)
	return err
}

func (p *PrometheusClient) SampleConfig() string {
	return sampleConfig
}

func (p *PrometheusClient) Description() string {
	return "Configuration for the Prometheus client to spawn"
}

// Implements prometheus.Collector
func (p *PrometheusClient) Describe(ch chan<- *prometheus.Desc) {
	prometheus.NewGauge(prometheus.GaugeOpts{Name: "Dummy", Help: "Dummy"}).Describe(ch)
}

// Expire removes Samples that have expired.
func (p *PrometheusClient) Expire() {
	now := p.now()
	for name, family := range p.fam {
		for key, sample := range family.Samples {
			if p.ExpirationInterval.Duration != 0 && now.After(sample.Expiration) {
				for k, _ := range sample.Labels {
					family.LabelSet[k]--
				}
				delete(family.Samples, key)

				if len(family.Samples) == 0 {
					delete(p.fam, name)
				}
			}
		}
	}
}

// Collect implements prometheus.Collector
func (p *PrometheusClient) Collect(ch chan<- prometheus.Metric) {
	p.Lock()
	defer p.Unlock()

	p.Expire()

	for name, family := range p.fam {
		// Get list of all labels on MetricFamily
		var labelNames []string
		for k, v := range family.LabelSet {
			if v > 0 {
				labelNames = append(labelNames, k)
			}
		}
		desc := prometheus.NewDesc(name, "Telegraf collected metric", labelNames, nil)

		for _, sample := range family.Samples {
			// Get labels for this sample; unset labels will be set to the
			// empty string
			var labels []string
			for _, label := range labelNames {
				v := sample.Labels[label]
				labels = append(labels, v)
			}

			var metric prometheus.Metric
			var err error
			switch family.TelegrafValueType {
			case telegraf.Summary:
				metric, err = prometheus.NewConstSummary(desc, sample.Count, sample.Sum, sample.SummaryValue, labels...)
			case telegraf.Histogram:
				metric, err = prometheus.NewConstHistogram(desc, sample.Count, sample.Sum, sample.HistogramValue, labels...)
			default:
				metric, err = prometheus.NewConstMetric(desc, getPromValueType(family.TelegrafValueType), sample.Value, labels...)
			}
			if err != nil {
				log.Printf("E! Error creating prometheus metric, "+
					"key: %s, labels: %v,\nerr: %s\n",
					name, labels, err.Error())
			}

			ch <- metric
		}
	}
}

func sanitize(value string) string {
	return invalidNameCharRE.ReplaceAllString(value, "_")
}

func getPromValueType(tt telegraf.ValueType) prometheus.ValueType {
	switch tt {
	case telegraf.Counter:
		return prometheus.CounterValue
	case telegraf.Gauge:
		return prometheus.GaugeValue
	default:
		return prometheus.UntypedValue
	}
}

// CreateSampleID creates a SampleID based on the tags of a telegraf.Metric.
func CreateSampleID(tags map[string]string) SampleID {
	pairs := make([]string, 0, len(tags))
	for k, v := range tags {
		pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
	}
	sort.Strings(pairs)
	return SampleID(strings.Join(pairs, ","))
}

func addSample(fam *MetricFamily, sample *Sample, sampleID SampleID) {

	for k, _ := range sample.Labels {
		fam.LabelSet[k]++
	}

	fam.Samples[sampleID] = sample
}

func (p *PrometheusClient) addMetricFamily(point telegraf.Metric, sample *Sample, mname string, sampleID SampleID) {
	var fam *MetricFamily
	var ok bool
	if fam, ok = p.fam[mname]; !ok {
		fam = &MetricFamily{
			Samples:           make(map[SampleID]*Sample),
			TelegrafValueType: point.Type(),
			LabelSet:          make(map[string]int),
		}
		p.fam[mname] = fam
	}

	addSample(fam, sample, sampleID)
}

func (p *PrometheusClient) Write(metrics []telegraf.Metric) error {
	p.Lock()
	defer p.Unlock()

	now := p.now()

	for _, point := range metrics {
		tags := point.Tags()
		sampleID := CreateSampleID(tags)

		labels := make(map[string]string)
		for k, v := range tags {
			labels[sanitize(k)] = v
		}

		// Prometheus doesn't have a string value type, so convert string
		// fields to labels.
		for fn, fv := range point.Fields() {
			switch fv := fv.(type) {
			case string:
				labels[sanitize(fn)] = fv
			}
		}

		switch point.Type() {
		case telegraf.Summary:
			var mname string
			var sum float64
			var count uint64
			summaryvalue := make(map[float64]float64)
			for fn, fv := range point.Fields() {
				var value float64
				switch fv := fv.(type) {
				case int64:
					value = float64(fv)
				case float64:
					value = fv
				default:
					continue
				}

				switch fn {
				case "sum":
					sum = value
				case "count":
					count = uint64(value)
				default:
					limit, err := strconv.ParseFloat(fn, 64)
					if err == nil {
						summaryvalue[limit] = value
					}
				}
			}
			sample := &Sample{
				Labels:       labels,
				SummaryValue: summaryvalue,
				Count:        count,
				Sum:          sum,
				Expiration:   now.Add(p.ExpirationInterval.Duration),
			}
			mname = sanitize(point.Name())

			p.addMetricFamily(point, sample, mname, sampleID)

		case telegraf.Histogram:
			var mname string
			var sum float64
			var count uint64
			histogramvalue := make(map[float64]uint64)
			for fn, fv := range point.Fields() {
				var value float64
				switch fv := fv.(type) {
				case int64:
					value = float64(fv)
				case float64:
					value = fv
				default:
					continue
				}

				switch fn {
				case "sum":
					sum = value
				case "count":
					count = uint64(value)
				default:
					limit, err := strconv.ParseFloat(fn, 64)
					if err == nil {
						histogramvalue[limit] = uint64(value)
					}
				}
			}
			sample := &Sample{
				Labels:         labels,
				HistogramValue: histogramvalue,
				Count:          count,
				Sum:            sum,
				Expiration:     now.Add(p.ExpirationInterval.Duration),
			}
			mname = sanitize(point.Name())

			p.addMetricFamily(point, sample, mname, sampleID)

		default:
			for fn, fv := range point.Fields() {
				// Ignore string and bool fields.
				var value float64
				switch fv := fv.(type) {
				case int64:
					value = float64(fv)
				case float64:
					value = fv
				default:
					continue
				}

				sample := &Sample{
					Labels:     labels,
					Value:      value,
					Expiration: now.Add(p.ExpirationInterval.Duration),
				}

				// Special handling of value field; supports passthrough from
				// the prometheus input.
				var mname string
				switch point.Type() {
				case telegraf.Counter:
					if fn == "counter" {
						mname = sanitize(point.Name())
					}
				case telegraf.Gauge:
					if fn == "gauge" {
						mname = sanitize(point.Name())
					}
				}
				if mname == "" {
					if fn == "value" {
						mname = sanitize(point.Name())
					} else {
						mname = sanitize(fmt.Sprintf("%s_%s", point.Name(), fn))
					}
				}

				p.addMetricFamily(point, sample, mname, sampleID)

			}
		}
	}
	return nil
}

func init() {
	outputs.Add("prometheus_client", func() telegraf.Output {
		return &PrometheusClient{
			ExpirationInterval: internal.Duration{Duration: time.Second * 60},
			fam:                make(map[string]*MetricFamily),
			now:                time.Now,
		}
	})
}
<commit_msg>Fix separation of multiple prometheus_client outputs (#3570)<commit_after>package prometheus_client

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/internal"
	"github.com/influxdata/telegraf/plugins/outputs"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var invalidNameCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)

// SampleID uniquely identifies a Sample
type SampleID string

// Sample represents the current value of a series.
type Sample struct {
	// Labels are the Prometheus labels.
	Labels map[string]string
	// Value is the value in the Prometheus output. Only one of these will populated.
	Value          float64
	HistogramValue map[float64]uint64
	SummaryValue   map[float64]float64
	// Histograms and Summaries need a count and a sum
	Count uint64
	Sum   float64
	// Expiration is the deadline that this Sample is valid until.
	Expiration time.Time
}

// MetricFamily contains the data required to build valid prometheus Metrics.
type MetricFamily struct {
	// Samples are the Sample belonging to this MetricFamily.
	Samples map[SampleID]*Sample
	// Need the telegraf ValueType because there isn't a Prometheus ValueType
	// representing Histogram or Summary
	TelegrafValueType telegraf.ValueType
	// LabelSet is the label counts for all Samples.
	LabelSet map[string]int
}

type PrometheusClient struct {
	Listen             string
	ExpirationInterval internal.Duration `toml:"expiration_interval"`
	Path               string            `toml:"path"`
	CollectorsExclude  []string          `toml:"collectors_exclude"`

	server *http.Server

	sync.Mutex
	// fam is the non-expired MetricFamily by Prometheus metric name.
	fam map[string]*MetricFamily
	// now returns the current time.
	now func() time.Time
}

var sampleConfig = `
  ## Address to listen on
  # listen = ":9273"

  ## Interval to expire metrics and not deliver to prometheus, 0 == no expiration
  # expiration_interval = "60s"

  ## Collectors to enable, valid entries are "gocollector" and "process".
  ## If unset, both are enabled.
  collectors_exclude = ["gocollector", "process"]
`

func (p *PrometheusClient) Start() error {
	defaultCollectors := map[string]bool{
		"gocollector": true,
		"process":     true,
	}
	for _, collector := range p.CollectorsExclude {
		delete(defaultCollectors, collector)
	}

	registry := prometheus.NewRegistry()
	for collector, _ := range defaultCollectors {
		switch collector {
		case "gocollector":
			registry.Register(prometheus.NewGoCollector())
		case "process":
			registry.Register(prometheus.NewProcessCollector(os.Getpid(), ""))
		default:
			return fmt.Errorf("unrecognized collector %s", collector)
		}
	}

	registry.Register(p)

	if p.Listen == "" {
		p.Listen = "localhost:9273"
	}

	if p.Path == "" {
		p.Path = "/metrics"
	}

	mux := http.NewServeMux()
	mux.Handle(p.Path, promhttp.HandlerFor(
		registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}))

	p.server = &http.Server{
		Addr:    p.Listen,
		Handler: mux,
	}

	go func() {
		if err := p.server.ListenAndServe(); err != nil {
			if err != http.ErrServerClosed {
				log.Printf("E! Error creating prometheus metric endpoint, err: %s\n",
					err.Error())
			}
		}
	}()
	return nil
}

func (p *PrometheusClient) Stop() {
	// plugin gets cleaned up in Close() already.
}

func (p *PrometheusClient) Connect() error {
	// This service output does not need to make any further connections
	return nil
}

func (p *PrometheusClient) Close() error {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
	defer cancel()
	err := p.server.Shutdown(ctx)
	prometheus.Unregister(p)
	return err
}

func (p *PrometheusClient) SampleConfig() string {
	return sampleConfig
}

func (p *PrometheusClient) Description() string {
	return "Configuration for the Prometheus client to spawn"
}

// Implements prometheus.Collector
func (p *PrometheusClient) Describe(ch chan<- *prometheus.Desc) {
	prometheus.NewGauge(prometheus.GaugeOpts{Name: "Dummy", Help: "Dummy"}).Describe(ch)
}

// Expire removes Samples that have expired.
func (p *PrometheusClient) Expire() {
	now := p.now()
	for name, family := range p.fam {
		for key, sample := range family.Samples {
			if p.ExpirationInterval.Duration != 0 && now.After(sample.Expiration) {
				for k, _ := range sample.Labels {
					family.LabelSet[k]--
				}
				delete(family.Samples, key)

				if len(family.Samples) == 0 {
					delete(p.fam, name)
				}
			}
		}
	}
}

// Collect implements prometheus.Collector
func (p *PrometheusClient) Collect(ch chan<- prometheus.Metric) {
	p.Lock()
	defer p.Unlock()

	p.Expire()

	for name, family := range p.fam {
		// Get list of all labels on MetricFamily
		var labelNames []string
		for k, v := range family.LabelSet {
			if v > 0 {
				labelNames = append(labelNames, k)
			}
		}
		desc := prometheus.NewDesc(name, "Telegraf collected metric", labelNames, nil)

		for _, sample := range family.Samples {
			// Get labels for this sample; unset labels will be set to the
			// empty string
			var labels []string
			for _, label := range labelNames {
				v := sample.Labels[label]
				labels = append(labels, v)
			}

			var metric prometheus.Metric
			var err error
			switch family.TelegrafValueType {
			case telegraf.Summary:
				metric, err = prometheus.NewConstSummary(desc, sample.Count, sample.Sum, sample.SummaryValue, labels...)
			case telegraf.Histogram:
				metric, err = prometheus.NewConstHistogram(desc, sample.Count, sample.Sum, sample.HistogramValue, labels...)
			default:
				metric, err = prometheus.NewConstMetric(desc, getPromValueType(family.TelegrafValueType), sample.Value, labels...)
			}
			if err != nil {
				log.Printf("E! Error creating prometheus metric, "+
					"key: %s, labels: %v,\nerr: %s\n",
					name, labels, err.Error())
			}

			ch <- metric
		}
	}
}

func sanitize(value string) string {
	return invalidNameCharRE.ReplaceAllString(value, "_")
}

func getPromValueType(tt telegraf.ValueType) prometheus.ValueType {
	switch tt {
	case telegraf.Counter:
		return prometheus.CounterValue
	case telegraf.Gauge:
		return prometheus.GaugeValue
	default:
		return prometheus.UntypedValue
	}
}

// CreateSampleID creates a SampleID based on the tags of a telegraf.Metric.
func CreateSampleID(tags map[string]string) SampleID {
	pairs := make([]string, 0, len(tags))
	for k, v := range tags {
		pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
	}
	sort.Strings(pairs)
	return SampleID(strings.Join(pairs, ","))
}

func addSample(fam *MetricFamily, sample *Sample, sampleID SampleID) {

	for k, _ := range sample.Labels {
		fam.LabelSet[k]++
	}

	fam.Samples[sampleID] = sample
}

func (p *PrometheusClient) addMetricFamily(point telegraf.Metric, sample *Sample, mname string, sampleID SampleID) {
	var fam *MetricFamily
	var ok bool
	if fam, ok = p.fam[mname]; !ok {
		fam = &MetricFamily{
			Samples:           make(map[SampleID]*Sample),
			TelegrafValueType: point.Type(),
			LabelSet:          make(map[string]int),
		}
		p.fam[mname] = fam
	}

	addSample(fam, sample, sampleID)
}

func (p *PrometheusClient) Write(metrics []telegraf.Metric) error {
	p.Lock()
	defer p.Unlock()

	now := p.now()

	for _, point := range metrics {
		tags := point.Tags()
		sampleID := CreateSampleID(tags)

		labels := make(map[string]string)
		for k, v := range tags {
			labels[sanitize(k)] = v
		}

		// Prometheus doesn't have a string value type, so convert string
		// fields to labels.
		for fn, fv := range point.Fields() {
			switch fv := fv.(type) {
			case string:
				labels[sanitize(fn)] = fv
			}
		}

		switch point.Type() {
		case telegraf.Summary:
			var mname string
			var sum float64
			var count uint64
			summaryvalue := make(map[float64]float64)
			for fn, fv := range point.Fields() {
				var value float64
				switch fv := fv.(type) {
				case int64:
					value = float64(fv)
				case float64:
					value = fv
				default:
					continue
				}

				switch fn {
				case "sum":
					sum = value
				case "count":
					count = uint64(value)
				default:
					limit, err := strconv.ParseFloat(fn, 64)
					if err == nil {
						summaryvalue[limit] = value
					}
				}
			}
			sample := &Sample{
				Labels:       labels,
				SummaryValue: summaryvalue,
				Count:        count,
				Sum:          sum,
				Expiration:   now.Add(p.ExpirationInterval.Duration),
			}
			mname = sanitize(point.Name())

			p.addMetricFamily(point, sample, mname, sampleID)

		case telegraf.Histogram:
			var mname string
			var sum float64
			var count uint64
			histogramvalue := make(map[float64]uint64)
			for fn, fv := range point.Fields() {
				var value float64
				switch fv := fv.(type) {
				case int64:
					value = float64(fv)
				case float64:
					value = fv
				default:
					continue
				}

				switch fn {
				case "sum":
					sum = value
				case "count":
					count = uint64(value)
				default:
					limit, err := strconv.ParseFloat(fn, 64)
					if err == nil {
						histogramvalue[limit] = uint64(value)
					}
				}
			}
			sample := &Sample{
				Labels:         labels,
				HistogramValue: histogramvalue,
				Count:          count,
				Sum:            sum,
				Expiration:     now.Add(p.ExpirationInterval.Duration),
			}
			mname = sanitize(point.Name())

			p.addMetricFamily(point, sample, mname, sampleID)

		default:
			for fn, fv := range point.Fields() {
				// Ignore string and bool fields.
				var value float64
				switch fv := fv.(type) {
				case int64:
					value = float64(fv)
				case float64:
					value = fv
				default:
					continue
				}

				sample := &Sample{
					Labels:     labels,
					Value:      value,
					Expiration: now.Add(p.ExpirationInterval.Duration),
				}

				// Special handling of value field; supports passthrough from
				// the prometheus input.
				var mname string
				switch point.Type() {
				case telegraf.Counter:
					if fn == "counter" {
						mname = sanitize(point.Name())
					}
				case telegraf.Gauge:
					if fn == "gauge" {
						mname = sanitize(point.Name())
					}
				}
				if mname == "" {
					if fn == "value" {
						mname = sanitize(point.Name())
					} else {
						mname = sanitize(fmt.Sprintf("%s_%s", point.Name(), fn))
					}
				}

				p.addMetricFamily(point, sample, mname, sampleID)

			}
		}
	}
	return nil
}

func init() {
	outputs.Add("prometheus_client", func() telegraf.Output {
		return &PrometheusClient{
			ExpirationInterval: internal.Duration{Duration: time.Second * 60},
			fam:                make(map[string]*MetricFamily),
			now:                time.Now,
		}
	})
}
<|endoftext|>


<commit_before>package js

import (
	"errors"
	"fmt"
	"strings"
	"testing"

	"github.com/orktes/orlang/ast"
	"github.com/orktes/orlang/types"

	"github.com/orktes/orlang/analyser"
	"github.com/orktes/orlang/parser"
	"github.com/robertkrimen/otto"
)

func TestSimple(t *testing.T) {
	res, err := testCodegen(`
		macro stringCat {
			() : (
				fn +(left:string, right:float32) => string {
					return left + num_to_str(float64(right))
				}

				fn +(left:string, right:float64) => string {
					return left + num_to_str(float64(right))
				}

				fn +(left:string, right:int32) => string {
					return left + num_to_str(float64(right))
				}

				fn +(left:string, right:int64) => string {
					return left + num_to_str(float64(right))
				}

				fn +(left:int32, right:string) => string {
					return num_to_str(float64(left)) + right
				}

				fn +(left:int64, right:string) => string {
					return num_to_str(float64(left)) + right
				}

				fn +(left:float32, right:string) => string {
					return num_to_str(float64(left)) + right
				}

				fn +(left:float64, right:string) => string {
					return num_to_str(float64(left)) + right
				}
			)
		}

		macro print {
			($a:expr) : (
				(fn () => void {
					stringCat!()
					print($a)
				})()
			)

			($a:expr, $( $x:expr ),*) : (
				print!(
					$a
          $(
						+
						" "
            +
            $x
          )*
				)
			)
		}

    macro createTuple {
      ($a:expr , $( $x:expr ),*) : (
        (
          $a
          $(
            ,
            $x
          )*
        )
      )
    }

		struct CustomStruct {
			var foo = 1
			var bar = 0

			fn +(left:CustomStruct, right:CustomStruct) => int32 {
				return left.foo + right.foo
			}

			fn foobar(a: int32) => int32 {
				return this.foo * a
			}

			fn incrementBar() {
				this.bar = this.bar + 1
			}
		}

    fn getData() => (int32, int32) {
      return createTuple!(1, 2)
    }

    fn sum(a : float64, b : float64 = float64(100.0)) => int32 {
      return int32(a + b)
    }

		fn callback(cb : ((int32) => void) = fn (a : int32) {}) {
			cb(1)
		}

    var ab = getData()

    fn main() {

      var (a, b) : (int32, int32) = ab
      var abSum = sum(
        b: float64(b),
        a: float64(a)
      )

      var negative = int64(-((1 + 4) * int32(5.5)))
      var (x, y, (h, j)) = (1, 2, ab)

      var counter = 0

      for counter < 10 {
				var a = 100 // This should not affect result
        counter++
      }

			var sum100 = sum(a: float64(1))

			callback(fn (a : int32) {
				sum100 = sum100 + a
			})

			fn +(left:int32, right:int32) => int32 {
				return left - right
			}

			fn -(left:int32, right:float32) => int32 {
				return left - int32(right)
			}

			var overloaded = (10 + 9) - 1.0

			var structSum = CustomStruct{10,10} + CustomStruct{10,10}
			var foobarValue = (CustomStruct{}).foobar(100)

			var structVal = CustomStruct{}
			structVal.incrementBar()
			structVal.incrementBar()
			structVal.incrementBar()
			structVal.incrementBar()

      if true {
        print!(
          "result is:",
          abSum - 1.5,
          "and",
          h + j,
          "and",
        	negative,
          "and",
          counter,
					"and",
          sum100,
					"and",
          overloaded,
					"and",
					structSum,
					"and",
					foobarValue,
					"and",
					structVal.bar
        )
      } else if false {
        print("Will not ever be here")
      } else {
				print("New else")
			}
    }
  `)

	if err != nil {
		t.Fatal(err)
	}

	if res != "result is:  2 and -1 and -25 and 10 and 102 and 0 and 20 and 100 and 4" {
		t.Error("Wrong result received", res)
	}
}

func testCodegen(str string) (string, error) {
	file, err := parser.Parse(strings.NewReader(str))
	if err != nil {
		return "", err
	}

	analyser, err := analyser.New(file)
	if err != nil {
		return "", err
	}

	analyser.AddExternalFunc("num_to_str", &types.SignatureType{
		ArgumentNames: []string{"num"},
		ArgumentTypes: []types.Type{types.Float64Type},
		ReturnType:    types.StringType,
		Extern:        true,
	})

	analyser.AddExternalFunc("print", &types.SignatureType{
		ArgumentNames: []string{"str"},
		ArgumentTypes: []types.Type{types.StringType},
		ReturnType:    types.VoidType,
		Extern:        true,
	})

	analyser.AddExternalFunc("printInt", &types.SignatureType{
		ArgumentNames: []string{"num"},
		ArgumentTypes: []types.Type{types.Int64Type},
		ReturnType:    types.VoidType,
		Extern:        true,
	})

	var analyErr error
	analyser.Error = func(node ast.Node, msg string, fatal bool) {
		if fatal {
			errStr := fmt.Sprintf(
				"%d:%d %s",
				node.StartPos().Line+1,
				node.StartPos().Column+1,
				msg,
			)
			analyErr = errors.New(errStr)
		}
	}

	info, err := analyser.Analyse()
	if err != nil {
		return "", err
	}

	if analyErr != nil {
		return "", analyErr
	}

	code := New(info).Generate(file)
	println(string(code)) // debug

	var result string
	vm := otto.New()
	vm.Set("print", func(call otto.FunctionCall) otto.Value {
		result = call.Argument(0).String()
		return otto.Value{}
	})

	vm.Set("num_to_str", func(call otto.FunctionCall) otto.Value {
		result = call.Argument(0).String()
		val, _ := vm.ToValue(result)
		return val
	})

	vm.Set("printInt", func(call otto.FunctionCall) otto.Value {
		resultInt, _ := call.Argument(0).ToInteger()
		result = fmt.Sprintf("%d", resultInt)
		return otto.Value{}
	})
	_, err = vm.Run(string(code))

	return result, err
}
<commit_msg>Fix result string<commit_after>package js

import (
	"errors"
	"fmt"
	"strings"
	"testing"

	"github.com/orktes/orlang/ast"
	"github.com/orktes/orlang/types"

	"github.com/orktes/orlang/analyser"
	"github.com/orktes/orlang/parser"
	"github.com/robertkrimen/otto"
)

func TestSimple(t *testing.T) {
	res, err := testCodegen(`
		macro stringCat {
			() : (
				fn +(left:string, right:float32) => string {
					return left + num_to_str(float64(right))
				}

				fn +(left:string, right:float64) => string {
					return left + num_to_str(float64(right))
				}

				fn +(left:string, right:int32) => string {
					return left + num_to_str(float64(right))
				}

				fn +(left:string, right:int64) => string {
					return left + num_to_str(float64(right))
				}

				fn +(left:int32, right:string) => string {
					return num_to_str(float64(left)) + right
				}

				fn +(left:int64, right:string) => string {
					return num_to_str(float64(left)) + right
				}

				fn +(left:float32, right:string) => string {
					return num_to_str(float64(left)) + right
				}

				fn +(left:float64, right:string) => string {
					return num_to_str(float64(left)) + right
				}
			)
		}

		macro print {
			($a:expr) : (
				(fn () => void {
					stringCat!()
					print($a)
				})()
			)

			($a:expr, $( $x:expr ),*) : (
				print!(
					$a
          $(
						+
						" "
            +
            $x
          )*
				)
			)
		}

    macro createTuple {
      ($a:expr , $( $x:expr ),*) : (
        (
          $a
          $(
            ,
            $x
          )*
        )
      )
    }

		struct CustomStruct {
			var foo = 1
			var bar = 0

			fn +(left:CustomStruct, right:CustomStruct) => int32 {
				return left.foo + right.foo
			}

			fn foobar(a: int32) => int32 {
				return this.foo * a
			}

			fn incrementBar() {
				this.bar = this.bar + 1
			}
		}

    fn getData() => (int32, int32) {
      return createTuple!(1, 2)
    }

    fn sum(a : float64, b : float64 = float64(100.0)) => int32 {
      return int32(a + b)
    }

		fn callback(cb : ((int32) => void) = fn (a : int32) {}) {
			cb(1)
		}

    var ab = getData()

    fn main() {

      var (a, b) : (int32, int32) = ab
      var abSum = sum(
        b: float64(b),
        a: float64(a)
      )

      var negative = int64(-((1 + 4) * int32(5.5)))
      var (x, y, (h, j)) = (1, 2, ab)

      var counter = 0

      for counter < 10 {
				var a = 100 // This should not affect result
        counter++
      }

			var sum100 = sum(a: float64(1))

			callback(fn (a : int32) {
				sum100 = sum100 + a
			})

			fn +(left:int32, right:int32) => int32 {
				return left - right
			}

			fn -(left:int32, right:float32) => int32 {
				return left - int32(right)
			}

			var overloaded = (10 + 9) - 1.0

			var structSum = CustomStruct{10,10} + CustomStruct{10,10}
			var foobarValue = (CustomStruct{}).foobar(100)

			var structVal = CustomStruct{}
			structVal.incrementBar()
			structVal.incrementBar()
			structVal.incrementBar()
			structVal.incrementBar()

      if true {
        print!(
          "result is:",
          abSum - 1.5,
          "and",
          h + j,
          "and",
        	negative,
          "and",
          counter,
					"and",
          sum100,
					"and",
          overloaded,
					"and",
					structSum,
					"and",
					foobarValue,
					"and",
					structVal.bar
        )
      } else if false {
        print("Will not ever be here")
      } else {
				print("New else")
			}
    }
  `)

	if err != nil {
		t.Fatal(err)
	}

	if res != "result is: 2 and -1 and -25 and 10 and 102 and 0 and 20 and 100 and 4" {
		t.Error("Wrong result received", res)
	}
}

func testCodegen(str string) (string, error) {
	file, err := parser.Parse(strings.NewReader(str))
	if err != nil {
		return "", err
	}

	analyser, err := analyser.New(file)
	if err != nil {
		return "", err
	}

	analyser.AddExternalFunc("num_to_str", &types.SignatureType{
		ArgumentNames: []string{"num"},
		ArgumentTypes: []types.Type{types.Float64Type},
		ReturnType:    types.StringType,
		Extern:        true,
	})

	analyser.AddExternalFunc("print", &types.SignatureType{
		ArgumentNames: []string{"str"},
		ArgumentTypes: []types.Type{types.StringType},
		ReturnType:    types.VoidType,
		Extern:        true,
	})

	analyser.AddExternalFunc("printInt", &types.SignatureType{
		ArgumentNames: []string{"num"},
		ArgumentTypes: []types.Type{types.Int64Type},
		ReturnType:    types.VoidType,
		Extern:        true,
	})

	var analyErr error
	analyser.Error = func(node ast.Node, msg string, fatal bool) {
		if fatal {
			errStr := fmt.Sprintf(
				"%d:%d %s",
				node.StartPos().Line+1,
				node.StartPos().Column+1,
				msg,
			)
			analyErr = errors.New(errStr)
		}
	}

	info, err := analyser.Analyse()
	if err != nil {
		return "", err
	}

	if analyErr != nil {
		return "", analyErr
	}

	code := New(info).Generate(file)
	println(string(code)) // debug

	var result string
	vm := otto.New()
	vm.Set("print", func(call otto.FunctionCall) otto.Value {
		result = call.Argument(0).String()
		return otto.Value{}
	})

	vm.Set("num_to_str", func(call otto.FunctionCall) otto.Value {
		result = call.Argument(0).String()
		val, _ := vm.ToValue(result)
		return val
	})

	vm.Set("printInt", func(call otto.FunctionCall) otto.Value {
		resultInt, _ := call.Argument(0).ToInteger()
		result = fmt.Sprintf("%d", resultInt)
		return otto.Value{}
	})
	_, err = vm.Run(string(code))

	return result, err
}
<|endoftext|>


<commit_before>package flame

import (
	"time"

	"github.com/256dpi/fire"
	"github.com/256dpi/fire/coal"

	"github.com/asaskevich/govalidator"
	"github.com/globalsign/mgo/bson"
	"golang.org/x/crypto/bcrypt"
)

// TokenType defines the type of a token.
type TokenType string

const (
	// AccessToken defines an access token.
	AccessToken TokenType = "access"

	// RefreshToken defines a refresh token.
	RefreshToken TokenType = "refresh"
)

// GenericToken is the interface that must be implemented by the tokens.
type GenericToken interface {
	coal.Model

	// DescribeToken should return a the token type field.
	DescribeToken() (typeField string)

	// GetTokenData should collect and return the tokens data.
	GetTokenData() (typ TokenType, scope []string, expiresAt time.Time, client bson.ObjectId, resourceOwner *bson.ObjectId)

	// SetTokenData should set the specified token data.
	SetTokenData(typ TokenType, scope []string, expiresAt time.Time, client Client, resourceOwner ResourceOwner)
}

// Token is the built-in model used to store access and refresh tokens.
type Token struct {
	coal.Base     `json:"-" bson:",inline" coal:"tokens:tokens"`
	Type          TokenType      `json:"type"`
	ExpiresAt     time.Time      `json:"expires-at" bson:"expires_at"`
	Scope         []string       `json:"scope" bson:"scope"`
	Client        bson.ObjectId  `json:"client-id" bson:"client_id"`
	ResourceOwner *bson.ObjectId `json:"resource-owner-id" bson:"resource_owner_id"`
}

// AddTokenIndexes will add access token indexes to the specified indexer.
func AddTokenIndexes(i *coal.Indexer, autoExpire bool) {
	i.Add(&Token{}, false, 0, "Type")
	i.Add(&Token{}, false, 0, "Client")
	i.Add(&Token{}, false, 0, "ResourceOwner")

	if autoExpire {
		i.Add(&Token{}, false, time.Minute, "ExpiresAt")
	}
}

// DescribeToken implements the flame.GenericToken interface.
func (t *Token) DescribeToken() string {
	return "Type"
}

// GetTokenData implements the flame.GenericToken interface.
func (t *Token) GetTokenData() (TokenType, []string, time.Time, bson.ObjectId, *bson.ObjectId) {
	return t.Type, t.Scope, t.ExpiresAt, t.Client, t.ResourceOwner
}

// SetTokenData implements the flame.GenericToken interface.
func (t *Token) SetTokenData(typ TokenType, scope []string, expiresAt time.Time, client Client, resourceOwner ResourceOwner) {
	t.Type = typ
	t.Scope = scope
	t.ExpiresAt = expiresAt
	t.Client = client.ID()
	if resourceOwner != nil {
		t.ResourceOwner = coal.P(resourceOwner.ID())
	}
}

// Validate implements the fire.ValidatableModel interface.
func (t *Token) Validate() error {
	// check id
	if !t.ID().Valid() {
		return fire.E("invalid id")
	}

	// check expires at
	if t.ExpiresAt.IsZero() {
		return fire.E("expires at not set")
	}

	return nil
}

// Client is the interface that must be implemented by clients.
type Client interface {
	coal.Model

	// DescribeClient should return a the clients identifier field.
	DescribeClient() (identifierField string)

	// ValidRedirectURL should return whether the specified redirect url can be
	// used by this client.
	//
	// Note: In order to increases security the callback should only allow
	// pre-registered redirect urls.
	ValidRedirectURL(string) bool

	// ValidSecret should determine whether the specified plain text secret
	// matches the stored hashed secret.
	ValidSecret(string) bool
}

// Application is the built-in model used to store clients.
type Application struct {
	coal.Base   `json:"-" bson:",inline" coal:"applications"`
	Name        string `json:"name" bson:"name"`
	Key         string `json:"key" bson:"key"`
	Secret      string `json:"secret,omitempty" bson:"-"`
	SecretHash  []byte `json:"-" bson:"secret"`
	RedirectURL string `json:"redirect-url" bson:"redirect_url"`
}

// AddApplicationIndexes will add application indexes to the specified indexer.
func AddApplicationIndexes(i *coal.Indexer) {
	i.Add(&Application{}, true, 0, "Key")
}

// DescribeClient implements the flame.Client interface.
func (a *Application) DescribeClient() string {
	return "Key"
}

// ValidRedirectURL implements the flame.Client interface.
func (a *Application) ValidRedirectURL(url string) bool {
	return url == a.RedirectURL
}

// ValidSecret implements the flame.Client interface.
func (a *Application) ValidSecret(secret string) bool {
	return bcrypt.CompareHashAndPassword(a.SecretHash, []byte(secret)) == nil
}

// Validate implements the coal.ValidatableModel interface.
func (a *Application) Validate() error {
	// hash password if available
	err := a.HashSecret()
	if err != nil {
		return err
	}

	// check id
	if !a.ID().Valid() {
		return fire.E("invalid id")
	}

	// check name
	if a.Name == "" {
		return fire.E("name not set")
	}

	// check key
	if a.Key == "" {
		return fire.E("key not set")
	}

	// check secret hash
	if len(a.SecretHash) == 0 {
		return fire.E("secret hash not set")
	}

	// check redirect uri
	if a.RedirectURL != "" && !govalidator.IsURL(a.RedirectURL) {
		return fire.E("invalid redirect url")
	}

	return nil
}

// HashSecret will hash Secret and set SecretHash.
func (a *Application) HashSecret() error {
	// check length
	if len(a.Secret) == 0 {
		return nil
	}

	// generate hash from password
	hash, err := bcrypt.GenerateFromPassword([]byte(a.Secret), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	// save hash
	a.SecretHash = hash

	// clear password
	a.Secret = ""

	return nil
}

// A ResourceOwnerDescription is returned by a ResourceOwner model to specify
// details about its implementation.
type ResourceOwnerDescription struct {
	IdentifierField string
}

// ResourceOwner is the interface that must be implemented resource owners.
type ResourceOwner interface {
	coal.Model

	// DescribeResourceOwner should return the resource owners identifier field.
	DescribeResourceOwner() (identifierField string)

	// ValidSecret should determine whether the specified plain text password
	// matches the stored hashed password.
	ValidPassword(string) bool
}

// User is the built-in model used to store resource owners.
type User struct {
	coal.Base    `json:"-" bson:",inline" coal:"users"`
	Name         string `json:"name" bson:"name"`
	Email        string `json:"email" bson:"email"`
	Password     string `json:"password,omitempty" bson:"-"`
	PasswordHash []byte `json:"-" bson:"password"`
}

// AddUserIndexes will add user indexes to the specified indexer.
func AddUserIndexes(i *coal.Indexer) {
	i.Add(&User{}, true, 0, "Email")
}

// DescribeResourceOwner implements the flame.ResourceOwner interface.
func (u *User) DescribeResourceOwner() string {
	return "Email"
}

// ValidPassword implements the flame.ResourceOwner interface.
func (u *User) ValidPassword(password string) bool {
	return bcrypt.CompareHashAndPassword(u.PasswordHash, []byte(password)) == nil
}

// Validate implements the coal.ValidatableModel interface.
func (u *User) Validate() error {
	// hash password if available
	err := u.HashPassword()
	if err != nil {
		return err
	}

	// check id
	if !u.ID().Valid() {
		return fire.E("invalid id")
	}

	// check name
	if u.Name == "" {
		return fire.E("name not set")
	}

	// check email
	if u.Email == "" || !govalidator.IsEmail(u.Email) {
		return fire.E("invalid email")
	}

	// check password hash
	if len(u.PasswordHash) == 0 {
		return fire.E("password hash not set")
	}

	return nil
}

// HashPassword will hash Password and set PasswordHash.
func (u *User) HashPassword() error {
	// check length
	if len(u.Password) == 0 {
		return nil
	}

	// generate hash from password
	hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	// save hash
	u.PasswordHash = hash

	// clear password
	u.Password = ""

	return nil
}

// TODO: Create a controller callback from HashSecret and HashPassword.
<commit_msg>removed todo<commit_after>package flame

import (
	"time"

	"github.com/256dpi/fire"
	"github.com/256dpi/fire/coal"

	"github.com/asaskevich/govalidator"
	"github.com/globalsign/mgo/bson"
	"golang.org/x/crypto/bcrypt"
)

// TokenType defines the type of a token.
type TokenType string

const (
	// AccessToken defines an access token.
	AccessToken TokenType = "access"

	// RefreshToken defines a refresh token.
	RefreshToken TokenType = "refresh"
)

// GenericToken is the interface that must be implemented by the tokens.
type GenericToken interface {
	coal.Model

	// DescribeToken should return a the token type field.
	DescribeToken() (typeField string)

	// GetTokenData should collect and return the tokens data.
	GetTokenData() (typ TokenType, scope []string, expiresAt time.Time, client bson.ObjectId, resourceOwner *bson.ObjectId)

	// SetTokenData should set the specified token data.
	SetTokenData(typ TokenType, scope []string, expiresAt time.Time, client Client, resourceOwner ResourceOwner)
}

// Token is the built-in model used to store access and refresh tokens.
type Token struct {
	coal.Base     `json:"-" bson:",inline" coal:"tokens:tokens"`
	Type          TokenType      `json:"type"`
	ExpiresAt     time.Time      `json:"expires-at" bson:"expires_at"`
	Scope         []string       `json:"scope" bson:"scope"`
	Client        bson.ObjectId  `json:"client-id" bson:"client_id"`
	ResourceOwner *bson.ObjectId `json:"resource-owner-id" bson:"resource_owner_id"`
}

// AddTokenIndexes will add access token indexes to the specified indexer.
func AddTokenIndexes(i *coal.Indexer, autoExpire bool) {
	i.Add(&Token{}, false, 0, "Type")
	i.Add(&Token{}, false, 0, "Client")
	i.Add(&Token{}, false, 0, "ResourceOwner")

	if autoExpire {
		i.Add(&Token{}, false, time.Minute, "ExpiresAt")
	}
}

// DescribeToken implements the flame.GenericToken interface.
func (t *Token) DescribeToken() string {
	return "Type"
}

// GetTokenData implements the flame.GenericToken interface.
func (t *Token) GetTokenData() (TokenType, []string, time.Time, bson.ObjectId, *bson.ObjectId) {
	return t.Type, t.Scope, t.ExpiresAt, t.Client, t.ResourceOwner
}

// SetTokenData implements the flame.GenericToken interface.
func (t *Token) SetTokenData(typ TokenType, scope []string, expiresAt time.Time, client Client, resourceOwner ResourceOwner) {
	t.Type = typ
	t.Scope = scope
	t.ExpiresAt = expiresAt
	t.Client = client.ID()
	if resourceOwner != nil {
		t.ResourceOwner = coal.P(resourceOwner.ID())
	}
}

// Validate implements the fire.ValidatableModel interface.
func (t *Token) Validate() error {
	// check id
	if !t.ID().Valid() {
		return fire.E("invalid id")
	}

	// check expires at
	if t.ExpiresAt.IsZero() {
		return fire.E("expires at not set")
	}

	return nil
}

// Client is the interface that must be implemented by clients.
type Client interface {
	coal.Model

	// DescribeClient should return a the clients identifier field.
	DescribeClient() (identifierField string)

	// ValidRedirectURL should return whether the specified redirect url can be
	// used by this client.
	//
	// Note: In order to increases security the callback should only allow
	// pre-registered redirect urls.
	ValidRedirectURL(string) bool

	// ValidSecret should determine whether the specified plain text secret
	// matches the stored hashed secret.
	ValidSecret(string) bool
}

// Application is the built-in model used to store clients.
type Application struct {
	coal.Base   `json:"-" bson:",inline" coal:"applications"`
	Name        string `json:"name" bson:"name"`
	Key         string `json:"key" bson:"key"`
	Secret      string `json:"secret,omitempty" bson:"-"`
	SecretHash  []byte `json:"-" bson:"secret"`
	RedirectURL string `json:"redirect-url" bson:"redirect_url"`
}

// AddApplicationIndexes will add application indexes to the specified indexer.
func AddApplicationIndexes(i *coal.Indexer) {
	i.Add(&Application{}, true, 0, "Key")
}

// DescribeClient implements the flame.Client interface.
func (a *Application) DescribeClient() string {
	return "Key"
}

// ValidRedirectURL implements the flame.Client interface.
func (a *Application) ValidRedirectURL(url string) bool {
	return url == a.RedirectURL
}

// ValidSecret implements the flame.Client interface.
func (a *Application) ValidSecret(secret string) bool {
	return bcrypt.CompareHashAndPassword(a.SecretHash, []byte(secret)) == nil
}

// Validate implements the coal.ValidatableModel interface.
func (a *Application) Validate() error {
	// hash password if available
	err := a.HashSecret()
	if err != nil {
		return err
	}

	// check id
	if !a.ID().Valid() {
		return fire.E("invalid id")
	}

	// check name
	if a.Name == "" {
		return fire.E("name not set")
	}

	// check key
	if a.Key == "" {
		return fire.E("key not set")
	}

	// check secret hash
	if len(a.SecretHash) == 0 {
		return fire.E("secret hash not set")
	}

	// check redirect uri
	if a.RedirectURL != "" && !govalidator.IsURL(a.RedirectURL) {
		return fire.E("invalid redirect url")
	}

	return nil
}

// HashSecret will hash Secret and set SecretHash.
func (a *Application) HashSecret() error {
	// check length
	if len(a.Secret) == 0 {
		return nil
	}

	// generate hash from password
	hash, err := bcrypt.GenerateFromPassword([]byte(a.Secret), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	// save hash
	a.SecretHash = hash

	// clear password
	a.Secret = ""

	return nil
}

// A ResourceOwnerDescription is returned by a ResourceOwner model to specify
// details about its implementation.
type ResourceOwnerDescription struct {
	IdentifierField string
}

// ResourceOwner is the interface that must be implemented resource owners.
type ResourceOwner interface {
	coal.Model

	// DescribeResourceOwner should return the resource owners identifier field.
	DescribeResourceOwner() (identifierField string)

	// ValidSecret should determine whether the specified plain text password
	// matches the stored hashed password.
	ValidPassword(string) bool
}

// User is the built-in model used to store resource owners.
type User struct {
	coal.Base    `json:"-" bson:",inline" coal:"users"`
	Name         string `json:"name" bson:"name"`
	Email        string `json:"email" bson:"email"`
	Password     string `json:"password,omitempty" bson:"-"`
	PasswordHash []byte `json:"-" bson:"password"`
}

// AddUserIndexes will add user indexes to the specified indexer.
func AddUserIndexes(i *coal.Indexer) {
	i.Add(&User{}, true, 0, "Email")
}

// DescribeResourceOwner implements the flame.ResourceOwner interface.
func (u *User) DescribeResourceOwner() string {
	return "Email"
}

// ValidPassword implements the flame.ResourceOwner interface.
func (u *User) ValidPassword(password string) bool {
	return bcrypt.CompareHashAndPassword(u.PasswordHash, []byte(password)) == nil
}

// Validate implements the coal.ValidatableModel interface.
func (u *User) Validate() error {
	// hash password if available
	err := u.HashPassword()
	if err != nil {
		return err
	}

	// check id
	if !u.ID().Valid() {
		return fire.E("invalid id")
	}

	// check name
	if u.Name == "" {
		return fire.E("name not set")
	}

	// check email
	if u.Email == "" || !govalidator.IsEmail(u.Email) {
		return fire.E("invalid email")
	}

	// check password hash
	if len(u.PasswordHash) == 0 {
		return fire.E("password hash not set")
	}

	return nil
}

// HashPassword will hash Password and set PasswordHash.
func (u *User) HashPassword() error {
	// check length
	if len(u.Password) == 0 {
		return nil
	}

	// generate hash from password
	hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	// save hash
	u.PasswordHash = hash

	// clear password
	u.Password = ""

	return nil
}
<|endoftext|>


<commit_before>package main

import (
        "syscall"
	"unsafe"
	"errors"
	"fmt"
	"sync/atomic"
)

type NetlinkSocket struct {
	fd int
	addr *syscall.SockaddrNetlink
}

func getNetlinkSocket(protocol int) (*NetlinkSocket, error) {
        fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, protocol)
        if err != nil {
                return nil, err
        }

	addr := syscall.SockaddrNetlink{Family: syscall.AF_NETLINK}
        if err := syscall.Bind(fd, &addr); err != nil {
                syscall.Close(fd)
                return nil, err
        }

	localaddr, err := syscall.Getsockname(fd)
	if err != nil {
		syscall.Close(fd)
		return nil, err
	}

	switch nladdr := localaddr.(type) {
        case *syscall.SockaddrNetlink:
		return &NetlinkSocket{fd: fd, addr: nladdr}, nil

	default:
		return nil, errors.New("Wrong socket address type")
        }
}

func (s *NetlinkSocket) Close() {
        syscall.Close(s.fd)
}

// Round the length of a netlink message up to align it properly.
func nlmsgAlign(len int) int {
        return (len + syscall.NLMSG_ALIGNTO - 1) & -syscall.NLMSG_ALIGNTO
}

// Round the length of a netlink route attribute up to align it
// properly.
func rtaAlign(len int) int {
        return (len + syscall.RTA_ALIGNTO - 1) & -syscall.RTA_ALIGNTO
}

type GenlMsghdr struct {
	cmd uint8
	version uint8
	reserved uint16
}

// reserved static generic netlink identifiers:
const (
	GENL_ID_GENERATE = 0
	GENL_ID_CTRL = syscall.NLMSG_MIN_TYPE
	GENL_ID_VFS_DQUOT = syscall.NLMSG_MIN_TYPE + 1
	GENL_ID_PMCRAID = syscall.NLMSG_MIN_TYPE + 2
)

const (
        CTRL_CMD_UNSPEC = 0
        CTRL_CMD_NEWFAMILY = 1
        CTRL_CMD_DELFAMILY = 2
        CTRL_CMD_GETFAMILY = 3
        CTRL_CMD_NEWOPS = 4
        CTRL_CMD_DELOPS = 5
        CTRL_CMD_GETOPS = 6
        CTRL_CMD_NEWMCAST_GRP = 7
        CTRL_CMD_DELMCAST_GRP = 8
)

const (
        CTRL_ATTR_UNSPEC = 0
        CTRL_ATTR_FAMILY_ID = 1
        CTRL_ATTR_FAMILY_NAME = 2
        CTRL_ATTR_VERSION = 3
        CTRL_ATTR_HDRSIZE = 4
        CTRL_ATTR_MAXATTR = 5
        CTRL_ATTR_OPS = 6
        CTRL_ATTR_MCAST_GROUPS = 7
)

func (s *NetlinkSocket) send(buf []byte) error {
	sa := syscall.SockaddrNetlink{
		Family: syscall.AF_NETLINK,
		Pid: 0,
		Groups: 0,
	}

	return syscall.Sendto(s.fd, buf, 0, &sa)
}

func (s *NetlinkSocket) recv(peer uint32) ([]byte, error) {
        rb := make([]byte, syscall.Getpagesize())
        nr, from, err := syscall.Recvfrom(s.fd, rb, 0)
        if err != nil {
                return nil, err
        }

	switch nlfrom := from.(type) {
        case *syscall.SockaddrNetlink:
		if (nlfrom.Pid != peer) {
			return nil, errors.New("netlink peer mismatch")
		}

		return rb[:nr], nil

	default:
		return nil, errors.New("Wrong socket address type")
        }
}

func (s *NetlinkSocket) validateNlMsghdr(buf []byte, seq uint32) (*syscall.NlMsghdr, error) {
	h := (*syscall.NlMsghdr)(unsafe.Pointer(&buf[0]))
	if len(buf) < syscall.NLMSG_HDRLEN || len(buf) < int(h.Len) {
		return nil, fmt.Errorf("truncated netlink message (got %d bytes, expected %d)", len(buf), h.Len)
	}

	fmt.Printf("XXX %v\n", h)

	if h.Pid != s.addr.Pid {
		return nil, fmt.Errorf("netlink reply pid mismatch (got %d, expected %d)", h.Pid, s.addr.Pid)
	}

	if h.Seq != seq {
		return nil, fmt.Errorf("netlink reply sequence number mismatch (got %d, expected %d)", h.Seq, seq)
	}

	payload := buf[syscall.NLMSG_HDRLEN:h.Len]

	if h.Type == syscall.NLMSG_ERROR {
		nlerr := (*syscall.NlMsgerr)(unsafe.Pointer(&payload[0]))

		if nlerr.Error == 0 {
			// An ack response
			return nil, nil
		}

		return nil, fmt.Errorf("netlink error reply: %s",
			syscall.Errno(-nlerr.Error).Error())
	}

	return h, nil
}

type NlMsg struct {
	buf []byte
}

func (nlmsg *NlMsg) Header() *syscall.NlMsghdr {
	return (*syscall.NlMsghdr)(unsafe.Pointer(&nlmsg.buf[0]))
}

func (nlmsg *NlMsg) Align(align int) {
	nlmsg.buf = nlmsg.buf[:(len(nlmsg.buf) + align - 1) & -align]
}

func (nlmsg *NlMsg) Alloc(size uintptr) unsafe.Pointer {
	l := len(nlmsg.buf)
	nlmsg.buf = nlmsg.buf[:l + int(size)]
	return unsafe.Pointer(&nlmsg.buf[l])
}

func NewNlMsg(flags uint16, typ uint16) *NlMsg {
	buf := make([]byte, syscall.NLMSG_HDRLEN, syscall.Getpagesize())
	nlmsg := &NlMsg{buf: buf}
	h := nlmsg.Header()
	h.Flags = flags
	h.Type = typ
	return nlmsg
}

var nextSeqNo uint32

func (nlmsg *NlMsg) Finish() (res []byte, seq uint32) {
	h := nlmsg.Header()
	h.Len = uint32(len(nlmsg.buf))
	seq = atomic.AddUint32(&nextSeqNo, 1)
	h.Seq = seq
	res = nlmsg.buf
	nlmsg.buf = nil
	return
}

func (nlmsg *NlMsg) AddGenlMsghdr(cmd uint8) (res *GenlMsghdr) {
	nlmsg.Align(syscall.NLMSG_ALIGNTO)
	res = (*GenlMsghdr)(nlmsg.Alloc(unsafe.Sizeof(*res)))
	res.cmd = cmd
	return
}

type RtAttr struct {
	pos int
	rta *syscall.RtAttr
}

func (nlmsg *NlMsg) BeginRtAttr(typ uint16) (res RtAttr) {
	nlmsg.Align(syscall.NLMSG_ALIGNTO)
	res.pos = len(nlmsg.buf)
	res.rta = (*syscall.RtAttr)(nlmsg.Alloc(unsafe.Sizeof(*res.rta)))
	res.rta.Type = typ
	return
}

func (nlmsg *NlMsg) FinishRtAttr(rta RtAttr) {
	rta.rta.Len = uint16(len(nlmsg.buf) - rta.pos)
}

func (nlmsg *NlMsg) AddAttr(typ uint16, str string) {
	rta := nlmsg.BeginRtAttr(typ)
	nlmsg.Align(syscall.RTA_ALIGNTO)
	l := len(nlmsg.buf)
	n := copy(nlmsg.buf[l:cap(nlmsg.buf)], str)
	fmt.Printf("XXX %d %d\n", l, n)
	nlmsg.buf = nlmsg.buf[:l + n + 1]
	nlmsg.buf[l + n] = 0
	nlmsg.FinishRtAttr(rta)
}

func (s *NetlinkSocket) resolveFamily() {
	nlmsg := NewNlMsg(syscall.NLM_F_REQUEST | syscall.NLM_F_ACK,
		GENL_ID_CTRL)

	nlmsg.AddGenlMsghdr(CTRL_CMD_GETFAMILY)
	nlmsg.AddAttr(CTRL_ATTR_FAMILY_NAME, "ovs_datapath")
	b, seq := nlmsg.Finish()
	fmt.Printf("DDD %v\n", b)

	if err := s.send(b); err != nil {
                panic(err)
        }

	rb, err := s.recv(0)
        if err != nil {
                panic(err)
        }

	_, err = s.validateNlMsghdr(rb, seq)
	if err != nil {
		panic(err)
	}
}
<commit_msg>Remove dead code<commit_after>package main

import (
        "syscall"
	"unsafe"
	"errors"
	"fmt"
	"sync/atomic"
)

type NetlinkSocket struct {
	fd int
	addr *syscall.SockaddrNetlink
}

func getNetlinkSocket(protocol int) (*NetlinkSocket, error) {
        fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, protocol)
        if err != nil {
                return nil, err
        }

	addr := syscall.SockaddrNetlink{Family: syscall.AF_NETLINK}
        if err := syscall.Bind(fd, &addr); err != nil {
                syscall.Close(fd)
                return nil, err
        }

	localaddr, err := syscall.Getsockname(fd)
	if err != nil {
		syscall.Close(fd)
		return nil, err
	}

	switch nladdr := localaddr.(type) {
        case *syscall.SockaddrNetlink:
		return &NetlinkSocket{fd: fd, addr: nladdr}, nil

	default:
		return nil, errors.New("Wrong socket address type")
        }
}

func (s *NetlinkSocket) Close() {
        syscall.Close(s.fd)
}

type GenlMsghdr struct {
	cmd uint8
	version uint8
	reserved uint16
}

// reserved static generic netlink identifiers:
const (
	GENL_ID_GENERATE = 0
	GENL_ID_CTRL = syscall.NLMSG_MIN_TYPE
	GENL_ID_VFS_DQUOT = syscall.NLMSG_MIN_TYPE + 1
	GENL_ID_PMCRAID = syscall.NLMSG_MIN_TYPE + 2
)

const (
        CTRL_CMD_UNSPEC = 0
        CTRL_CMD_NEWFAMILY = 1
        CTRL_CMD_DELFAMILY = 2
        CTRL_CMD_GETFAMILY = 3
        CTRL_CMD_NEWOPS = 4
        CTRL_CMD_DELOPS = 5
        CTRL_CMD_GETOPS = 6
        CTRL_CMD_NEWMCAST_GRP = 7
        CTRL_CMD_DELMCAST_GRP = 8
)

const (
        CTRL_ATTR_UNSPEC = 0
        CTRL_ATTR_FAMILY_ID = 1
        CTRL_ATTR_FAMILY_NAME = 2
        CTRL_ATTR_VERSION = 3
        CTRL_ATTR_HDRSIZE = 4
        CTRL_ATTR_MAXATTR = 5
        CTRL_ATTR_OPS = 6
        CTRL_ATTR_MCAST_GROUPS = 7
)

func (s *NetlinkSocket) send(buf []byte) error {
	sa := syscall.SockaddrNetlink{
		Family: syscall.AF_NETLINK,
		Pid: 0,
		Groups: 0,
	}

	return syscall.Sendto(s.fd, buf, 0, &sa)
}

func (s *NetlinkSocket) recv(peer uint32) ([]byte, error) {
        rb := make([]byte, syscall.Getpagesize())
        nr, from, err := syscall.Recvfrom(s.fd, rb, 0)
        if err != nil {
                return nil, err
        }

	switch nlfrom := from.(type) {
        case *syscall.SockaddrNetlink:
		if (nlfrom.Pid != peer) {
			return nil, errors.New("netlink peer mismatch")
		}

		return rb[:nr], nil

	default:
		return nil, errors.New("Wrong socket address type")
        }
}

func (s *NetlinkSocket) validateNlMsghdr(buf []byte, seq uint32) (*syscall.NlMsghdr, error) {
	h := (*syscall.NlMsghdr)(unsafe.Pointer(&buf[0]))
	if len(buf) < syscall.NLMSG_HDRLEN || len(buf) < int(h.Len) {
		return nil, fmt.Errorf("truncated netlink message (got %d bytes, expected %d)", len(buf), h.Len)
	}

	fmt.Printf("XXX %v\n", h)

	if h.Pid != s.addr.Pid {
		return nil, fmt.Errorf("netlink reply pid mismatch (got %d, expected %d)", h.Pid, s.addr.Pid)
	}

	if h.Seq != seq {
		return nil, fmt.Errorf("netlink reply sequence number mismatch (got %d, expected %d)", h.Seq, seq)
	}

	payload := buf[syscall.NLMSG_HDRLEN:h.Len]

	if h.Type == syscall.NLMSG_ERROR {
		nlerr := (*syscall.NlMsgerr)(unsafe.Pointer(&payload[0]))

		if nlerr.Error == 0 {
			// An ack response
			return nil, nil
		}

		return nil, fmt.Errorf("netlink error reply: %s",
			syscall.Errno(-nlerr.Error).Error())
	}

	return h, nil
}

type NlMsg struct {
	buf []byte
}

func (nlmsg *NlMsg) Header() *syscall.NlMsghdr {
	return (*syscall.NlMsghdr)(unsafe.Pointer(&nlmsg.buf[0]))
}

func (nlmsg *NlMsg) Align(align int) {
	nlmsg.buf = nlmsg.buf[:(len(nlmsg.buf) + align - 1) & -align]
}

func (nlmsg *NlMsg) Alloc(size uintptr) unsafe.Pointer {
	l := len(nlmsg.buf)
	nlmsg.buf = nlmsg.buf[:l + int(size)]
	return unsafe.Pointer(&nlmsg.buf[l])
}

func NewNlMsg(flags uint16, typ uint16) *NlMsg {
	buf := make([]byte, syscall.NLMSG_HDRLEN, syscall.Getpagesize())
	nlmsg := &NlMsg{buf: buf}
	h := nlmsg.Header()
	h.Flags = flags
	h.Type = typ
	return nlmsg
}

var nextSeqNo uint32

func (nlmsg *NlMsg) Finish() (res []byte, seq uint32) {
	h := nlmsg.Header()
	h.Len = uint32(len(nlmsg.buf))
	seq = atomic.AddUint32(&nextSeqNo, 1)
	h.Seq = seq
	res = nlmsg.buf
	nlmsg.buf = nil
	return
}

func (nlmsg *NlMsg) AddGenlMsghdr(cmd uint8) (res *GenlMsghdr) {
	nlmsg.Align(syscall.NLMSG_ALIGNTO)
	res = (*GenlMsghdr)(nlmsg.Alloc(unsafe.Sizeof(*res)))
	res.cmd = cmd
	return
}

type RtAttr struct {
	pos int
	rta *syscall.RtAttr
}

func (nlmsg *NlMsg) BeginRtAttr(typ uint16) (res RtAttr) {
	nlmsg.Align(syscall.NLMSG_ALIGNTO)
	res.pos = len(nlmsg.buf)
	res.rta = (*syscall.RtAttr)(nlmsg.Alloc(unsafe.Sizeof(*res.rta)))
	res.rta.Type = typ
	return
}

func (nlmsg *NlMsg) FinishRtAttr(rta RtAttr) {
	rta.rta.Len = uint16(len(nlmsg.buf) - rta.pos)
}

func (nlmsg *NlMsg) AddAttr(typ uint16, str string) {
	rta := nlmsg.BeginRtAttr(typ)
	nlmsg.Align(syscall.RTA_ALIGNTO)
	l := len(nlmsg.buf)
	n := copy(nlmsg.buf[l:cap(nlmsg.buf)], str)
	fmt.Printf("XXX %d %d\n", l, n)
	nlmsg.buf = nlmsg.buf[:l + n + 1]
	nlmsg.buf[l + n] = 0
	nlmsg.FinishRtAttr(rta)
}

func (s *NetlinkSocket) resolveFamily() {
	nlmsg := NewNlMsg(syscall.NLM_F_REQUEST | syscall.NLM_F_ACK,
		GENL_ID_CTRL)

	nlmsg.AddGenlMsghdr(CTRL_CMD_GETFAMILY)
	nlmsg.AddAttr(CTRL_ATTR_FAMILY_NAME, "ovs_datapath")
	b, seq := nlmsg.Finish()
	fmt.Printf("DDD %v\n", b)

	if err := s.send(b); err != nil {
                panic(err)
        }

	rb, err := s.recv(0)
        if err != nil {
                panic(err)
        }

	_, err = s.validateNlMsghdr(rb, seq)
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package firewaller_test

import (
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/dummy"
	"launchpad.net/juju-core/juju/testing"
	"launchpad.net/juju-core/state"
	coretesting "launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/worker/firewaller"
	"reflect"
	stdtesting "testing"
	"time"
)

func TestPackage(t *stdtesting.T) {
	coretesting.MgoTestPackage(t)
}

// assertPorts retrieves the open ports of the instance and compares them
// to the expected.
func assertPorts(c *C, inst environs.Instance, machineId int, expected []state.Port) {
	start := time.Now()
	for {
		got, err := inst.Ports(machineId)
		if err != nil {
			c.Fatal(err)
			return
		}
		state.SortPorts(got)
		state.SortPorts(expected)
		if reflect.DeepEqual(got, expected) {
			c.Succeed()
			return
		}
		if time.Since(start) > 5000*time.Millisecond {
			c.Fatalf("timed out: expected %q; got %q", expected, got)
			return
		}
		time.Sleep(50 * time.Millisecond)
	}
	panic("unreachable")
}

type FirewallerSuite struct {
	testing.JujuConnSuite
	op    <-chan dummy.Operation
	charm *state.Charm
}

var _ = Suite(&FirewallerSuite{})

func (s *FirewallerSuite) SetUpTest(c *C) {
	s.JujuConnSuite.SetUpTest(c)
	s.charm = s.AddTestingCharm(c, "dummy")
}

func (s *FirewallerSuite) TestStartStop(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	c.Assert(fw.Stop(), IsNil)
}

func (s *FirewallerSuite) addUnit(c *C, svc *state.Service) (*state.Unit, *state.Machine) {
	units, err := s.Conn.AddUnits(svc, 1)
	c.Assert(err, IsNil)
	u := units[0]
	id, err := u.AssignedMachineId()
	c.Assert(err, IsNil)
	m, err := s.State.Machine(id)
	c.Assert(err, IsNil)
	return u, m
}

// startInstance starts a new instance for the given machine.
func (s *FirewallerSuite) startInstance(c *C, m *state.Machine) environs.Instance {
	inst, err := s.Conn.Environ.StartInstance(m.Id(), s.StateInfo(c), nil)
	c.Assert(err, IsNil)
	err = m.SetInstanceId(inst.Id())
	c.Assert(err, IsNil)
	return inst
}

func (s *FirewallerSuite) TestNotExposedService(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.Conn.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	u, m := s.addUnit(c, svc)
	inst := s.startInstance(c, m)

	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)

	assertPorts(c, inst, m.Id(), nil)

	err = u.ClosePort("tcp", 80)
	c.Assert(err, IsNil)

	assertPorts(c, inst, m.Id(), nil)
}

func (s *FirewallerSuite) TestExposedService(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.Conn.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)

	err = svc.SetExposed()
	c.Assert(err, IsNil)
	u, m := s.addUnit(c, svc)
	inst := s.startInstance(c, m)

	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)

	assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})

	err = u.ClosePort("tcp", 80)
	c.Assert(err, IsNil)

	assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 8080}})
}

func (s *FirewallerSuite) TestMachineWithoutInstanceId(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.Conn.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	err = svc.SetExposed()
	c.Assert(err, IsNil)
	// add a unit but don't start its instance yet.
	u1, m1 := s.addUnit(c, svc)

	// add another unit and start its instance, so that
	// we're sure the firewaller has seen the first instance.
	u2, m2 := s.addUnit(c, svc)
	inst2 := s.startInstance(c, m2)
	err = u2.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	assertPorts(c, inst2, m2.Id(), []state.Port{{"tcp", 80}})

	inst1 := s.startInstance(c, m1)
	err = u1.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)
	assertPorts(c, inst1, m1.Id(), []state.Port{{"tcp", 8080}})
}

func (s *FirewallerSuite) TestMultipleUnits(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.State.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	err = svc.SetExposed()
	c.Assert(err, IsNil)

	u1, m1 := s.addUnit(c, svc)
	inst1 := s.startInstance(c, m1)
	err = u1.OpenPort("tcp", 80)
	c.Assert(err, IsNil)

	u2, m2 := s.addUnit(c, svc)
	inst2 := s.startInstance(c, m2)
	err = u2.OpenPort("tcp", 80)
	c.Assert(err, IsNil)

	assertPorts(c, inst1, m1.Id(), []state.Port{{"tcp", 80}})
	assertPorts(c, inst2, m2.Id(), []state.Port{{"tcp", 80}})

	err = u1.ClosePort("tcp", 80)
	c.Assert(err, IsNil)
	err = u2.ClosePort("tcp", 80)
	c.Assert(err, IsNil)

	assertPorts(c, inst1, m1.Id(), nil)
	assertPorts(c, inst2, m2.Id(), nil)
}

func (s *FirewallerSuite) TestFirewallerStartWithState(c *C) {
	svc, err := s.State.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	err = svc.SetExposed()
	c.Assert(err, IsNil)
	u, m := s.addUnit(c, svc)
	inst := s.startInstance(c, m)

	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)

	// Nothing open without firewaller.
	assertPorts(c, inst, m.Id(), nil)

	// Starting the firewaller opens the ports.
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})

	err = svc.SetExposed()
	c.Assert(err, IsNil)
}

func (s *FirewallerSuite) TestFirewallerStartWithPartialState(c *C) {
	m, err := s.State.AddMachine()
	c.Assert(err, IsNil)
	inst, err := s.Conn.Environ.StartInstance(m.Id(), s.StateInfo(c), nil)
	c.Assert(err, IsNil)
	err = m.SetInstanceId(inst.Id())
	c.Assert(err, IsNil)

	svc, err := s.State.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	err = svc.SetExposed()
	c.Assert(err, IsNil)

	// Starting the firewaller, no open ports.
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	assertPorts(c, inst, m.Id(), nil)

	// Complete steps to open port.
	u, err := svc.AddUnit()
	c.Assert(err, IsNil)
	err = u.AssignToMachine(m)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)

	assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 80}})
}

func (s *FirewallerSuite) TestSetClearExposedService(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.State.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)

	u, m := s.addUnit(c, svc)
	inst := s.startInstance(c, m)
	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)

	// Not exposed service, so no open port.
	assertPorts(c, inst, m.Id(), nil)

	// SeExposed opens the ports.
	err = svc.SetExposed()
	c.Assert(err, IsNil)

	assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})

	// ClearExposed closes the ports again.
	err = svc.ClearExposed()
	c.Assert(err, IsNil)

	assertPorts(c, inst, m.Id(), nil)
}
<commit_msg>Add StartSync.<commit_after>package firewaller_test

import (
	. "launchpad.net/gocheck"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/dummy"
	"launchpad.net/juju-core/juju/testing"
	"launchpad.net/juju-core/state"
	coretesting "launchpad.net/juju-core/testing"
	"launchpad.net/juju-core/worker/firewaller"
	"reflect"
	stdtesting "testing"
	"time"
)

func TestPackage(t *stdtesting.T) {
	coretesting.MgoTestPackage(t)
}

type FirewallerSuite struct {
	testing.JujuConnSuite
	op    <-chan dummy.Operation
	charm *state.Charm
}

// assertPorts retrieves the open ports of the instance and compares them
// to the expected.
func (s *FirewallerSuite) assertPorts(c *C, inst environs.Instance, machineId int, expected []state.Port) {
	s.State.StartSync()
	start := time.Now()
	for {
		got, err := inst.Ports(machineId)
		if err != nil {
			c.Fatal(err)
			return
		}
		state.SortPorts(got)
		state.SortPorts(expected)
		if reflect.DeepEqual(got, expected) {
			c.Succeed()
			return
		}
		if time.Since(start) > 5*time.Second {
			c.Fatalf("timed out: expected %q; got %q", expected, got)
			return
		}
		time.Sleep(50 * time.Millisecond)
	}
	panic("unreachable")
}

var _ = Suite(&FirewallerSuite{})

func (s *FirewallerSuite) SetUpTest(c *C) {
	s.JujuConnSuite.SetUpTest(c)
	s.charm = s.AddTestingCharm(c, "dummy")
}

func (s *FirewallerSuite) TestStartStop(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	c.Assert(fw.Stop(), IsNil)
}

func (s *FirewallerSuite) addUnit(c *C, svc *state.Service) (*state.Unit, *state.Machine) {
	units, err := s.Conn.AddUnits(svc, 1)
	c.Assert(err, IsNil)
	u := units[0]
	id, err := u.AssignedMachineId()
	c.Assert(err, IsNil)
	m, err := s.State.Machine(id)
	c.Assert(err, IsNil)
	return u, m
}

// startInstance starts a new instance for the given machine.
func (s *FirewallerSuite) startInstance(c *C, m *state.Machine) environs.Instance {
	inst, err := s.Conn.Environ.StartInstance(m.Id(), s.StateInfo(c), nil)
	c.Assert(err, IsNil)
	err = m.SetInstanceId(inst.Id())
	c.Assert(err, IsNil)
	return inst
}

func (s *FirewallerSuite) TestNotExposedService(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.Conn.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	u, m := s.addUnit(c, svc)
	inst := s.startInstance(c, m)

	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)

	s.assertPorts(c, inst, m.Id(), nil)

	err = u.ClosePort("tcp", 80)
	c.Assert(err, IsNil)

	s.assertPorts(c, inst, m.Id(), nil)
}

func (s *FirewallerSuite) TestExposedService(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.Conn.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)

	err = svc.SetExposed()
	c.Assert(err, IsNil)
	u, m := s.addUnit(c, svc)
	inst := s.startInstance(c, m)

	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)

	s.assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})

	err = u.ClosePort("tcp", 80)
	c.Assert(err, IsNil)

	s.assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 8080}})
}

func (s *FirewallerSuite) TestMachineWithoutInstanceId(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.Conn.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	err = svc.SetExposed()
	c.Assert(err, IsNil)
	// add a unit but don't start its instance yet.
	u1, m1 := s.addUnit(c, svc)

	// add another unit and start its instance, so that
	// we're sure the firewaller has seen the first instance.
	u2, m2 := s.addUnit(c, svc)
	inst2 := s.startInstance(c, m2)
	err = u2.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	s.assertPorts(c, inst2, m2.Id(), []state.Port{{"tcp", 80}})

	inst1 := s.startInstance(c, m1)
	err = u1.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)
	s.assertPorts(c, inst1, m1.Id(), []state.Port{{"tcp", 8080}})
}

func (s *FirewallerSuite) TestMultipleUnits(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.State.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	err = svc.SetExposed()
	c.Assert(err, IsNil)

	u1, m1 := s.addUnit(c, svc)
	inst1 := s.startInstance(c, m1)
	err = u1.OpenPort("tcp", 80)
	c.Assert(err, IsNil)

	u2, m2 := s.addUnit(c, svc)
	inst2 := s.startInstance(c, m2)
	err = u2.OpenPort("tcp", 80)
	c.Assert(err, IsNil)

	s.assertPorts(c, inst1, m1.Id(), []state.Port{{"tcp", 80}})
	s.assertPorts(c, inst2, m2.Id(), []state.Port{{"tcp", 80}})

	err = u1.ClosePort("tcp", 80)
	c.Assert(err, IsNil)
	err = u2.ClosePort("tcp", 80)
	c.Assert(err, IsNil)

	s.assertPorts(c, inst1, m1.Id(), nil)
	s.assertPorts(c, inst2, m2.Id(), nil)
}

func (s *FirewallerSuite) TestFirewallerStartWithState(c *C) {
	svc, err := s.State.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	err = svc.SetExposed()
	c.Assert(err, IsNil)
	u, m := s.addUnit(c, svc)
	inst := s.startInstance(c, m)

	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)

	// Nothing open without firewaller.
	s.assertPorts(c, inst, m.Id(), nil)

	// Starting the firewaller opens the ports.
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	s.assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})

	err = svc.SetExposed()
	c.Assert(err, IsNil)
}

func (s *FirewallerSuite) TestFirewallerStartWithPartialState(c *C) {
	m, err := s.State.AddMachine()
	c.Assert(err, IsNil)
	inst, err := s.Conn.Environ.StartInstance(m.Id(), s.StateInfo(c), nil)
	c.Assert(err, IsNil)
	err = m.SetInstanceId(inst.Id())
	c.Assert(err, IsNil)

	svc, err := s.State.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)
	err = svc.SetExposed()
	c.Assert(err, IsNil)

	// Starting the firewaller, no open ports.
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	s.assertPorts(c, inst, m.Id(), nil)

	// Complete steps to open port.
	u, err := svc.AddUnit()
	c.Assert(err, IsNil)
	err = u.AssignToMachine(m)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)

	s.assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 80}})
}

func (s *FirewallerSuite) TestSetClearExposedService(c *C) {
	fw := firewaller.NewFirewaller(s.State)
	defer func() { c.Assert(fw.Stop(), IsNil) }()

	svc, err := s.State.AddService("wordpress", s.charm)
	c.Assert(err, IsNil)

	u, m := s.addUnit(c, svc)
	inst := s.startInstance(c, m)
	err = u.OpenPort("tcp", 80)
	c.Assert(err, IsNil)
	err = u.OpenPort("tcp", 8080)
	c.Assert(err, IsNil)

	// Not exposed service, so no open port.
	s.assertPorts(c, inst, m.Id(), nil)

	// SeExposed opens the ports.
	err = svc.SetExposed()
	c.Assert(err, IsNil)

	s.assertPorts(c, inst, m.Id(), []state.Port{{"tcp", 80}, {"tcp", 8080}})

	// ClearExposed closes the ports again.
	err = svc.ClearExposed()
	c.Assert(err, IsNil)

	s.assertPorts(c, inst, m.Id(), nil)
}
<|endoftext|>