<commit_before>package diffstore

import (
	"fmt"
	"image"
	"net"
	"net/http/httptest"
	"path"
	"path/filepath"
	"sort"
	"testing"

	"google.golang.org/grpc"

	assert "github.com/stretchr/testify/require"

	"go.skia.org/infra/go/fileutil"
	"go.skia.org/infra/go/testutils"
	"go.skia.org/infra/go/tiling"
	"go.skia.org/infra/go/timer"
	"go.skia.org/infra/go/util"
	"go.skia.org/infra/golden/go/diff"
	"go.skia.org/infra/golden/go/types"
)

const (
	TEST_N_DIGESTS = 20

	// Prefix for the image url handler.
	IMAGE_URL_PREFIX = "/img/"
)

func TestMemDiffStore(t *testing.T) {
	testutils.LargeTest(t)

	// Get a small tile and get them cached.
	w, cleanup := testutils.TempDir(t)
	defer cleanup()
	baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-diffstore")
	client, tile := getSetupAndTile(t, baseDir)

	mapper := NewGoldDiffStoreMapper(&diff.DiffMetrics{})
	diffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
	assert.NoError(t, err)
	memDiffStore := diffStore.(*MemDiffStore)

	testDiffStore(t, tile, baseDir, diffStore, memDiffStore)
}

type DummyDiffStoreMapper struct {
	GoldDiffStoreMapper
}

func (d DummyDiffStoreMapper) DiffFn(leftImg *image.NRGBA, rightImg *image.NRGBA) (interface{}, *image.NRGBA) {
	return 42, nil
}

func TestDiffFn(t *testing.T) {
	testutils.LargeTest(t)

	w, cleanup := testutils.TempDir(t)
	defer cleanup()
	baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-difffn")
	client, _ := getSetupAndTile(t, baseDir)

	// Instantiate a new MemDiffStore with the DummyDiffFn.
	mapper := DummyDiffStoreMapper{GoldDiffStoreMapper: NewGoldDiffStoreMapper(&diff.DiffMetrics{}).(GoldDiffStoreMapper)}
	diffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)

	assert.NoError(t, err)
	memDiffStore := diffStore.(*MemDiffStore)
	img1 := image.NewNRGBA(image.Rect(1, 2, 3, 4))
	img2 := image.NewNRGBA(image.Rect(9, 8, 7, 6))

	// Check that proper values are returned by the diff function.
	diffMetrics, diffImg := memDiffStore.mapper.DiffFn(img1, img2)
	assert.Equal(t, 42, diffMetrics)
	assert.Nil(t, diffImg)
}

func TestNetDiffStore(t *testing.T) {
	testutils.LargeTest(t)

	w, cleanup := testutils.TempDir(t)
	defer cleanup()
	baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-netdiffstore")
	client, tile := getSetupAndTile(t, baseDir)

	mapper := NewGoldDiffStoreMapper(&diff.DiffMetrics{})
	memDiffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
	assert.NoError(t, err)

	// Start the server that wraps around the MemDiffStore.
	codec := MetricMapCodec{}
	serverImpl := NewDiffServiceServer(memDiffStore, codec)
	lis, err := net.Listen("tcp", "localhost:0")
	assert.NoError(t, err)

	// Start the grpc server.
	server := grpc.NewServer()
	RegisterDiffServiceServer(server, serverImpl)
	go func() {
		_ = server.Serve(lis)
	}()
	defer server.Stop()

	// Start the http server.
	imgHandler, err := memDiffStore.ImageHandler(IMAGE_URL_PREFIX)
	assert.NoError(t, err)

	httpServer := httptest.NewServer(imgHandler)
	defer func() { httpServer.Close() }()

	// Create the NetDiffStore.
	addr := lis.Addr().String()
	conn, err := grpc.Dial(addr, grpc.WithInsecure())
	assert.NoError(t, err)
	defer func() {
		assert.NoError(t, conn.Close())
	}()

	netDiffStore, err := NewNetDiffStore(conn, httpServer.Listener.Addr().String(), codec)
	assert.NoError(t, err)

	// run tests against it.
	testDiffStore(t, tile, baseDir, netDiffStore, memDiffStore.(*MemDiffStore))
}

func testDiffStore(t *testing.T, tile *tiling.Tile, baseDir string, diffStore diff.DiffStore, memDiffStore *MemDiffStore) {
	// Pick the test with highest number of digests.
	byName := map[string]util.StringSet{}
	for _, trace := range tile.Traces {
		gTrace := trace.(*types.GoldenTrace)
		name := gTrace.Params_[types.PRIMARY_KEY_FIELD]
		if _, ok := byName[name]; !ok {
			byName[name] = util.StringSet{}
		}
		byName[name].AddLists(gTrace.Values)
	}
	testDigests := make([][]string, 0, len(byName))
	for _, digests := range byName {
		delete(digests, types.MISSING_DIGEST)
		testDigests = append(testDigests, digests.Keys())
	}
	sort.Sort(digestsSlice(testDigests))

	// Warm the digests and make sure they are in the cache.
	digests := testDigests[0][:TEST_N_DIGESTS]
	diffStore.WarmDigests(diff.PRIORITY_NOW, digests, false)
	memDiffStore.imgLoader.Sync()
	for _, d := range digests {
		assert.True(t, memDiffStore.imgLoader.IsOnDisk(d), fmt.Sprintf("Could not find '%s'", d))
	}

	// Warm the diffs and make sure they are in the cache.
	diffStore.WarmDiffs(diff.PRIORITY_NOW, digests, digests)
	memDiffStore.sync()

	diffIDs := make([]string, 0, len(digests)*len(digests))
	for _, d1 := range digests {
		for _, d2 := range digests {
			if d1 != d2 {
				id := memDiffStore.mapper.DiffID(d1, d2)
				diffIDs = append(diffIDs, id)
				assert.True(t, memDiffStore.diffMetricsCache.Contains(id))
			}
		}
	}

	// Get the results and make sure they are correct.
	foundDiffs := make(map[string]map[string]interface{}, len(digests))
	ti := timer.New("Get warmed diffs.")
	for _, oneDigest := range digests {
		found, err := diffStore.Get(diff.PRIORITY_NOW, oneDigest, digests)
		assert.NoError(t, err)
		foundDiffs[oneDigest] = found

		// Load the diff from disk and compare.
		for twoDigest, dr := range found {
			id := memDiffStore.mapper.DiffID(oneDigest, twoDigest)
			loadedDr, err := memDiffStore.metricsStore.loadDiffMetrics(id)
			assert.NoError(t, err)
			assert.Equal(t, dr, loadedDr, "Comparing: %s", id)
		}
	}
	ti.Stop()
	testDiffs(t, baseDir, memDiffStore, digests, digests, foundDiffs)

	// Get the results directly and make sure they are correct.
	digests = testDigests[1][:TEST_N_DIGESTS]
	ti = timer.New("Get cold diffs")
	foundDiffs = make(map[string]map[string]interface{}, len(digests))
	for _, oneDigest := range digests {
		found, err := diffStore.Get(diff.PRIORITY_NOW, oneDigest, digests)
		assert.NoError(t, err)
		foundDiffs[oneDigest] = found
	}
	ti.Stop()
	memDiffStore.sync()
	testDiffs(t, baseDir, memDiffStore, digests, digests, foundDiffs)

	// Diff against an arbitrary GCS location.
	gcsImgID := GCSPathToImageID(TEST_GCS_SECONDARY_BUCKET, TEST_PATH_IMG_1)
	foundDiffs = map[string]map[string]interface{}{}
	for _, oneDigest := range digests {
		found, err := diffStore.Get(diff.PRIORITY_NOW, oneDigest, []string{gcsImgID})
		assert.NoError(t, err)
		assert.Equal(t, 1, len(found))
		foundDiffs[oneDigest] = found
	}
	testDiffs(t, baseDir, memDiffStore, digests, []string{gcsImgID}, foundDiffs)
}

func testDiffs(t *testing.T, baseDir string, diffStore *MemDiffStore, leftDigests, rightDigests []string, result map[string]map[string]interface{}) {
	for _, left := range leftDigests {
		for _, right := range rightDigests {
			if left != right {
				_, ok := result[left][right]
				assert.True(t, ok, fmt.Sprintf("left: %s, right:%s", left, right))
				diffPath := diffStore.mapper.DiffPath(left, right)
				assert.True(t, fileutil.FileExists(filepath.Join(diffStore.localDiffDir, diffPath)), fmt.Sprintf("Could not find %s", diffPath))
			}
		}
	}
}

type DummyDiffMetrics struct {
	NumDiffPixels     int
	PercentDiffPixels float32
}

type digestsSlice [][]string

func (d digestsSlice) Len() int           { return len(d) }
func (d digestsSlice) Less(i, j int) bool { return len(d[i]) > len(d[j]) }
func (d digestsSlice) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
<commit_msg>Fix race in diffserver test<commit_after>package diffstore

import (
	"fmt"
	"image"
	"net"
	"net/http/httptest"
	"path"
	"path/filepath"
	"sort"
	"testing"

	"google.golang.org/grpc"

	assert "github.com/stretchr/testify/require"

	"go.skia.org/infra/go/fileutil"
	"go.skia.org/infra/go/testutils"
	"go.skia.org/infra/go/tiling"
	"go.skia.org/infra/go/timer"
	"go.skia.org/infra/go/util"
	"go.skia.org/infra/golden/go/diff"
	"go.skia.org/infra/golden/go/types"
)

const (
	TEST_N_DIGESTS = 20

	// Prefix for the image url handler.
	IMAGE_URL_PREFIX = "/img/"
)

func TestMemDiffStore(t *testing.T) {
	testutils.LargeTest(t)

	// Get a small tile and get them cached.
	w, cleanup := testutils.TempDir(t)
	defer cleanup()
	baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-diffstore")
	client, tile := getSetupAndTile(t, baseDir)

	mapper := NewGoldDiffStoreMapper(&diff.DiffMetrics{})
	diffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
	assert.NoError(t, err)
	memDiffStore := diffStore.(*MemDiffStore)

	testDiffStore(t, tile, baseDir, diffStore, memDiffStore)
}

type DummyDiffStoreMapper struct {
	GoldDiffStoreMapper
}

func (d DummyDiffStoreMapper) DiffFn(leftImg *image.NRGBA, rightImg *image.NRGBA) (interface{}, *image.NRGBA) {
	return 42, nil
}

func TestDiffFn(t *testing.T) {
	testutils.LargeTest(t)

	w, cleanup := testutils.TempDir(t)
	defer cleanup()
	baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-difffn")
	client, _ := getSetupAndTile(t, baseDir)

	// Instantiate a new MemDiffStore with the DummyDiffFn.
	mapper := DummyDiffStoreMapper{GoldDiffStoreMapper: NewGoldDiffStoreMapper(&diff.DiffMetrics{}).(GoldDiffStoreMapper)}
	diffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)

	assert.NoError(t, err)
	memDiffStore := diffStore.(*MemDiffStore)
	img1 := image.NewNRGBA(image.Rect(1, 2, 3, 4))
	img2 := image.NewNRGBA(image.Rect(9, 8, 7, 6))

	// Check that proper values are returned by the diff function.
	diffMetrics, diffImg := memDiffStore.mapper.DiffFn(img1, img2)
	assert.Equal(t, 42, diffMetrics)
	assert.Nil(t, diffImg)
}

func TestNetDiffStore(t *testing.T) {
	testutils.LargeTest(t)

	w, cleanup := testutils.TempDir(t)
	defer cleanup()
	baseDir := path.Join(w, TEST_DATA_BASE_DIR+"-netdiffstore")
	client, tile := getSetupAndTile(t, baseDir)

	mapper := NewGoldDiffStoreMapper(&diff.DiffMetrics{})
	memDiffStore, err := NewMemDiffStore(client, baseDir, []string{TEST_GCS_BUCKET_NAME}, TEST_GCS_IMAGE_DIR, 10, mapper)
	assert.NoError(t, err)

	// Start the server that wraps around the MemDiffStore.
	codec := MetricMapCodec{}
	serverImpl := NewDiffServiceServer(memDiffStore, codec)
	lis, err := net.Listen("tcp", "localhost:0")
	assert.NoError(t, err)

	// Start the grpc server.
	server := grpc.NewServer()
	RegisterDiffServiceServer(server, serverImpl)
	go func() {
		_ = server.Serve(lis)
	}()
	defer server.Stop()

	// Start the http server.
	imgHandler, err := memDiffStore.ImageHandler(IMAGE_URL_PREFIX)
	assert.NoError(t, err)

	httpServer := httptest.NewServer(imgHandler)
	defer func() { httpServer.Close() }()

	// Create the NetDiffStore.
	addr := lis.Addr().String()
	conn, err := grpc.Dial(addr, grpc.WithInsecure())
	assert.NoError(t, err)
	defer func() {
		assert.NoError(t, conn.Close())
	}()

	netDiffStore, err := NewNetDiffStore(conn, httpServer.Listener.Addr().String(), codec)
	assert.NoError(t, err)

	// run tests against it.
	testDiffStore(t, tile, baseDir, netDiffStore, memDiffStore.(*MemDiffStore))
}

func testDiffStore(t *testing.T, tile *tiling.Tile, baseDir string, diffStore diff.DiffStore, memDiffStore *MemDiffStore) {
	// Pick the test with highest number of digests.
	byName := map[string]util.StringSet{}
	for _, trace := range tile.Traces {
		gTrace := trace.(*types.GoldenTrace)
		name := gTrace.Params_[types.PRIMARY_KEY_FIELD]
		if _, ok := byName[name]; !ok {
			byName[name] = util.StringSet{}
		}
		byName[name].AddLists(gTrace.Values)
	}
	testDigests := make([][]string, 0, len(byName))
	for _, digests := range byName {
		delete(digests, types.MISSING_DIGEST)
		testDigests = append(testDigests, digests.Keys())
	}
	sort.Sort(digestsSlice(testDigests))

	// Warm the digests and make sure they are in the cache.
	digests := testDigests[0][:TEST_N_DIGESTS]
	diffStore.WarmDigests(diff.PRIORITY_NOW, digests, false)
	memDiffStore.imgLoader.Sync()
	for _, d := range digests {
		assert.True(t, memDiffStore.imgLoader.IsOnDisk(d), fmt.Sprintf("Could not find '%s'", d))
	}

	// Warm the diffs and make sure they are in the cache.
	diffStore.WarmDiffs(diff.PRIORITY_NOW, digests, digests)
	memDiffStore.sync()

	diffIDs := make([]string, 0, len(digests)*len(digests))
	for _, d1 := range digests {
		for _, d2 := range digests {
			if d1 != d2 {
				id := memDiffStore.mapper.DiffID(d1, d2)
				diffIDs = append(diffIDs, id)
				assert.True(t, memDiffStore.diffMetricsCache.Contains(id))
			}
		}
	}

	// Get the results and make sure they are correct.
	foundDiffs := make(map[string]map[string]interface{}, len(digests))
	ti := timer.New("Get warmed diffs.")
	for _, oneDigest := range digests {
		found, err := diffStore.Get(diff.PRIORITY_NOW, oneDigest, digests)
		assert.NoError(t, err)
		foundDiffs[oneDigest] = found

		// Load the diff from disk and compare.
		for twoDigest, dr := range found {
			id := memDiffStore.mapper.DiffID(oneDigest, twoDigest)
			loadedDr, err := memDiffStore.metricsStore.loadDiffMetrics(id)
			assert.NoError(t, err)
			assert.Equal(t, dr, loadedDr, "Comparing: %s", id)
		}
	}
	ti.Stop()
	testDiffs(t, baseDir, memDiffStore, digests, digests, foundDiffs)

	// Get the results directly and make sure they are correct.
	digests = testDigests[1][:TEST_N_DIGESTS]
	ti = timer.New("Get cold diffs")
	foundDiffs = make(map[string]map[string]interface{}, len(digests))
	for _, oneDigest := range digests {
		found, err := diffStore.Get(diff.PRIORITY_NOW, oneDigest, digests)
		assert.NoError(t, err)
		foundDiffs[oneDigest] = found
	}
	ti.Stop()
	testDiffs(t, baseDir, memDiffStore, digests, digests, foundDiffs)

	// Diff against an arbitrary GCS location.
	gcsImgID := GCSPathToImageID(TEST_GCS_SECONDARY_BUCKET, TEST_PATH_IMG_1)
	foundDiffs = map[string]map[string]interface{}{}
	for _, oneDigest := range digests {
		found, err := diffStore.Get(diff.PRIORITY_NOW, oneDigest, []string{gcsImgID})
		assert.NoError(t, err)
		assert.Equal(t, 1, len(found))
		foundDiffs[oneDigest] = found
	}
	testDiffs(t, baseDir, memDiffStore, digests, []string{gcsImgID}, foundDiffs)
}

func testDiffs(t *testing.T, baseDir string, diffStore *MemDiffStore, leftDigests, rightDigests []string, result map[string]map[string]interface{}) {
	diffStore.sync()
	for _, left := range leftDigests {
		for _, right := range rightDigests {
			if left != right {
				_, ok := result[left][right]
				assert.True(t, ok, fmt.Sprintf("left: %s, right:%s", left, right))
				diffPath := diffStore.mapper.DiffPath(left, right)
				assert.True(t, fileutil.FileExists(filepath.Join(diffStore.localDiffDir, diffPath)), fmt.Sprintf("Could not find %s", diffPath))
			}
		}
	}
}

type DummyDiffMetrics struct {
	NumDiffPixels     int
	PercentDiffPixels float32
}

type digestsSlice [][]string

func (d digestsSlice) Len() int           { return len(d) }
func (d digestsSlice) Less(i, j int) bool { return len(d[i]) > len(d[j]) }
func (d digestsSlice) Swap(i, j int)      { d[i], d[j] = d[j], d[i] }
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"log"
	"strconv"
	"strings"

	"github.com/hashicorp/terraform/helper/schema"

	"google.golang.org/api/storage/v1"
)

func resourceStorageBucketAcl() *schema.Resource {
	return &schema.Resource{
		Create:        resourceStorageBucketAclCreate,
		Read:          resourceStorageBucketAclRead,
		Update:        resourceStorageBucketAclUpdate,
		Delete:        resourceStorageBucketAclDelete,
		CustomizeDiff: resourceStorageBucketAclCustomizeDiff,

		Schema: map[string]*schema.Schema{
			"bucket": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"default_acl": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"predefined_acl": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"role_entity"},
			},

			"role_entity": &schema.Schema{
				Type:          schema.TypeList,
				Optional:      true,
				Computed:      true,
				Elem:          &schema.Schema{Type: schema.TypeString},
				ConflictsWith: []string{"predefined_acl"},
			},
		},
	}
}

func resourceStorageBucketAclCustomizeDiff(diff *schema.ResourceDiff, meta interface{}) error {
	keys := diff.GetChangedKeysPrefix("role_entity")
	if len(keys) < 1 {
		return nil
	}
	count := diff.Get("role_entity.#").(int)
	if count < 1 {
		return nil
	}
	state := map[string]struct{}{}
	conf := map[string]struct{}{}
	for i := 0; i < count; i++ {
		old, new := diff.GetChange(fmt.Sprintf("role_entity.%d", i))
		state[old.(string)] = struct{}{}
		conf[new.(string)] = struct{}{}
	}
	if len(state) != len(conf) {
		return nil
	}
	for k, _ := range state {
		if _, ok := conf[k]; !ok {
			return nil
		}
	}
	return diff.Clear("role_entity")
}

type RoleEntity struct {
	Role   string
	Entity string
}

func getBucketAclId(bucket string) string {
	return bucket + "-acl"
}

func getRoleEntityPair(role_entity string) (*RoleEntity, error) {
	split := strings.Split(role_entity, ":")
	if len(split) != 2 {
		return nil, fmt.Errorf("Error, each role entity pair must be " +
			"formatted as ROLE:entity")
	}

	return &RoleEntity{Role: split[0], Entity: split[1]}, nil
}

func resourceStorageBucketAclCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	bucket := d.Get("bucket").(string)
	predefined_acl := ""
	default_acl := ""
	role_entity := make([]interface{}, 0)

	if v, ok := d.GetOk("predefined_acl"); ok {
		predefined_acl = v.(string)
	}

	if v, ok := d.GetOk("role_entity"); ok {
		role_entity = v.([]interface{})
	}

	if v, ok := d.GetOk("default_acl"); ok {
		default_acl = v.(string)
	}

	if len(predefined_acl) > 0 {
		res, err := config.clientStorage.Buckets.Get(bucket).Do()

		if err != nil {
			return fmt.Errorf("Error reading bucket %s: %v", bucket, err)
		}

		res, err = config.clientStorage.Buckets.Update(bucket,
			res).PredefinedAcl(predefined_acl).Do()

		if err != nil {
			return fmt.Errorf("Error updating bucket %s: %v", bucket, err)
		}

	}
	if len(role_entity) > 0 {
		current, err := config.clientStorage.BucketAccessControls.List(bucket).Do()
		if err != nil {
			return fmt.Errorf("Error retrieving current ACLs: %s", err)
		}
		for _, v := range role_entity {
			pair, err := getRoleEntityPair(v.(string))
			if err != nil {
				return err
			}
			var alreadyInserted bool
			for _, cur := range current.Items {
				if cur.Entity == pair.Entity && cur.Role == pair.Role {
					alreadyInserted = true
					break
				}
			}
			if alreadyInserted {
				log.Printf("[DEBUG]: pair %s-%s already exists, not trying to insert again\n", pair.Role, pair.Entity)
				continue
			}
			bucketAccessControl := &storage.BucketAccessControl{
				Role:   pair.Role,
				Entity: pair.Entity,
			}

			log.Printf("[DEBUG]: storing re %s-%s", pair.Role, pair.Entity)

			_, err = config.clientStorage.BucketAccessControls.Insert(bucket, bucketAccessControl).Do()

			if err != nil {
				return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err)
			}
		}

	}

	if len(default_acl) > 0 {
		res, err := config.clientStorage.Buckets.Get(bucket).Do()

		if err != nil {
			return fmt.Errorf("Error reading bucket %s: %v", bucket, err)
		}

		res, err = config.clientStorage.Buckets.Update(bucket,
			res).PredefinedDefaultObjectAcl(default_acl).Do()

		if err != nil {
			return fmt.Errorf("Error updating bucket %s: %v", bucket, err)
		}

	}

	d.SetId(getBucketAclId(bucket))
	return resourceStorageBucketAclRead(d, meta)
}

func resourceStorageBucketAclRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	bucket := d.Get("bucket").(string)

	// The API offers no way to retrieve predefined ACLs,
	// and we can't tell which access controls were created
	// by the predefined roles, so...
	//
	// This is, needless to say, a bad state of affairs and
	// should be fixed.
	if _, ok := d.GetOk("role_entity"); ok {
		res, err := config.clientStorage.BucketAccessControls.List(bucket).Do()

		if err != nil {
			return handleNotFoundError(err, d, fmt.Sprintf("Storage Bucket ACL for bucket %q", d.Get("bucket").(string)))
		}
		entities := make([]string, 0, len(res.Items))
		for _, item := range res.Items {
			entities = append(entities, item.Role+":"+item.Entity)
		}

		d.Set("role_entity", entities)
	}

	return nil
}

func resourceStorageBucketAclUpdate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	bucket := d.Get("bucket").(string)

	if d.HasChange("role_entity") {
		bkt, err := config.clientStorage.Buckets.Get(bucket).Do()
		if err != nil {
			return fmt.Errorf("Error reading bucket %q: %v", bucket, err)
		}

		project := strconv.FormatUint(bkt.ProjectNumber, 10)
		o, n := d.GetChange("role_entity")
		old_re, new_re := o.([]interface{}), n.([]interface{})

		old_re_map := make(map[string]string)
		for _, v := range old_re {
			res, err := getRoleEntityPair(v.(string))

			if err != nil {
				return fmt.Errorf(
					"Old state has malformed Role/Entity pair: %v", err)
			}

			old_re_map[res.Entity] = res.Role
		}

		for _, v := range new_re {
			pair, err := getRoleEntityPair(v.(string))

			bucketAccessControl := &storage.BucketAccessControl{
				Role:   pair.Role,
				Entity: pair.Entity,
			}

			// If the old state is missing this entity, it needs to be inserted
			if _, ok := old_re_map[pair.Entity]; !ok {
				_, err = config.clientStorage.BucketAccessControls.Insert(
					bucket, bucketAccessControl).Do()
			}

			// Now we only store the keys that have to be removed
			delete(old_re_map, pair.Entity)

			if err != nil {
				return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err)
			}
		}

		for entity, role := range old_re_map {
			if entity == fmt.Sprintf("project-owners-%s", project) && role == "OWNER" {
				log.Printf("Skipping %s-%s; not deleting owner ACL.", role, entity)
				continue
			}
			log.Printf("[DEBUG]: removing entity %s", entity)
			err := config.clientStorage.BucketAccessControls.Delete(bucket, entity).Do()

			if err != nil {
				return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err)
			}
		}

		return resourceStorageBucketAclRead(d, meta)
	}

	if d.HasChange("default_acl") {
		default_acl := d.Get("default_acl").(string)

		res, err := config.clientStorage.Buckets.Get(bucket).Do()

		if err != nil {
			return fmt.Errorf("Error reading bucket %s: %v", bucket, err)
		}

		res, err = config.clientStorage.Buckets.Update(bucket,
			res).PredefinedDefaultObjectAcl(default_acl).Do()

		if err != nil {
			return fmt.Errorf("Error updating bucket %s: %v", bucket, err)
		}

		return resourceStorageBucketAclRead(d, meta)
	}

	return nil
}

func resourceStorageBucketAclDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	bucket := d.Get("bucket").(string)

	bkt, err := config.clientStorage.Buckets.Get(bucket).Do()
	if err != nil {
		return fmt.Errorf("Error retrieving bucket %q: %v", bucket, err)
	}
	project := strconv.FormatUint(bkt.ProjectNumber, 10)

	re_local := d.Get("role_entity").([]interface{})
	for _, v := range re_local {
		res, err := getRoleEntityPair(v.(string))
		if err != nil {
			return err
		}

		if res.Entity == fmt.Sprintf("project-owners-%s", project) && res.Role == "OWNER" {
			log.Printf("Skipping %s-%s; not deleting owner ACL.", res.Role, res.Entity)
			continue
		}

		log.Printf("[DEBUG]: removing entity %s", res.Entity)

		err = config.clientStorage.BucketAccessControls.Delete(bucket, res.Entity).Do()

		if err != nil {
			return fmt.Errorf("Error deleting entity %s ACL: %s", res.Entity, err)
		}
	}

	return nil
}
<commit_msg>Fix failing test for storage ACLs.<commit_after>package google

import (
	"fmt"
	"log"
	"strconv"
	"strings"

	"github.com/hashicorp/terraform/helper/schema"

	"google.golang.org/api/storage/v1"
)

func resourceStorageBucketAcl() *schema.Resource {
	return &schema.Resource{
		Create:        resourceStorageBucketAclCreate,
		Read:          resourceStorageBucketAclRead,
		Update:        resourceStorageBucketAclUpdate,
		Delete:        resourceStorageBucketAclDelete,
		CustomizeDiff: resourceStorageBucketAclCustomizeDiff,

		Schema: map[string]*schema.Schema{
			"bucket": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"default_acl": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"predefined_acl": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				ForceNew:      true,
				ConflictsWith: []string{"role_entity"},
			},

			"role_entity": &schema.Schema{
				Type:          schema.TypeList,
				Optional:      true,
				Computed:      true,
				Elem:          &schema.Schema{Type: schema.TypeString},
				ConflictsWith: []string{"predefined_acl"},
			},
		},
	}
}

func resourceStorageBucketAclCustomizeDiff(diff *schema.ResourceDiff, meta interface{}) error {
	keys := diff.GetChangedKeysPrefix("role_entity")
	if len(keys) < 1 {
		return nil
	}
	count := diff.Get("role_entity.#").(int)
	if count < 1 {
		return nil
	}
	state := map[string]struct{}{}
	conf := map[string]struct{}{}
	for i := 0; i < count; i++ {
		old, new := diff.GetChange(fmt.Sprintf("role_entity.%d", i))
		state[old.(string)] = struct{}{}
		conf[new.(string)] = struct{}{}
	}
	if len(state) != len(conf) {
		return nil
	}
	for k, _ := range state {
		if _, ok := conf[k]; !ok {
			return nil
		}
	}
	return diff.Clear("role_entity")
}

type RoleEntity struct {
	Role   string
	Entity string
}

func getBucketAclId(bucket string) string {
	return bucket + "-acl"
}

func getRoleEntityPair(role_entity string) (*RoleEntity, error) {
	split := strings.Split(role_entity, ":")
	if len(split) != 2 {
		return nil, fmt.Errorf("Error, each role entity pair must be " +
			"formatted as ROLE:entity")
	}

	return &RoleEntity{Role: split[0], Entity: split[1]}, nil
}

func resourceStorageBucketAclCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	bucket := d.Get("bucket").(string)
	predefined_acl := ""
	default_acl := ""
	role_entity := make([]interface{}, 0)

	if v, ok := d.GetOk("predefined_acl"); ok {
		predefined_acl = v.(string)
	}

	if v, ok := d.GetOk("role_entity"); ok {
		role_entity = v.([]interface{})
	}

	if v, ok := d.GetOk("default_acl"); ok {
		default_acl = v.(string)
	}

	if len(predefined_acl) > 0 {
		res, err := config.clientStorage.Buckets.Get(bucket).Do()

		if err != nil {
			return fmt.Errorf("Error reading bucket %s: %v", bucket, err)
		}

		res, err = config.clientStorage.Buckets.Update(bucket,
			res).PredefinedAcl(predefined_acl).Do()

		if err != nil {
			return fmt.Errorf("Error updating bucket %s: %v", bucket, err)
		}

	}

	if len(role_entity) > 0 {
		current, err := config.clientStorage.BucketAccessControls.List(bucket).Do()
		if err != nil {
			return fmt.Errorf("Error retrieving current ACLs: %s", err)
		}
		for _, v := range role_entity {
			pair, err := getRoleEntityPair(v.(string))
			if err != nil {
				return err
			}
			var alreadyInserted bool
			for _, cur := range current.Items {
				if cur.Entity == pair.Entity && cur.Role == pair.Role {
					alreadyInserted = true
					break
				}
			}
			if alreadyInserted {
				log.Printf("[DEBUG]: pair %s-%s already exists, not trying to insert again\n", pair.Role, pair.Entity)
				continue
			}
			bucketAccessControl := &storage.BucketAccessControl{
				Role:   pair.Role,
				Entity: pair.Entity,
			}

			log.Printf("[DEBUG]: storing re %s-%s", pair.Role, pair.Entity)

			_, err = config.clientStorage.BucketAccessControls.Insert(bucket, bucketAccessControl).Do()

			if err != nil {
				return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err)
			}
		}

	}

	if len(default_acl) > 0 {
		res, err := config.clientStorage.Buckets.Get(bucket).Do()

		if err != nil {
			return fmt.Errorf("Error reading bucket %s: %v", bucket, err)
		}

		res, err = config.clientStorage.Buckets.Update(bucket,
			res).PredefinedDefaultObjectAcl(default_acl).Do()

		if err != nil {
			return fmt.Errorf("Error updating bucket %s: %v", bucket, err)
		}

	}

	d.SetId(getBucketAclId(bucket))
	return resourceStorageBucketAclRead(d, meta)
}

func resourceStorageBucketAclRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	bucket := d.Get("bucket").(string)

	// The API offers no way to retrieve predefined ACLs,
	// and we can't tell which access controls were created
	// by the predefined roles, so...
	//
	// This is, needless to say, a bad state of affairs and
	// should be fixed.
	if _, ok := d.GetOk("role_entity"); ok {
		res, err := config.clientStorage.BucketAccessControls.List(bucket).Do()

		if err != nil {
			return handleNotFoundError(err, d, fmt.Sprintf("Storage Bucket ACL for bucket %q", d.Get("bucket").(string)))
		}
		entities := make([]string, 0, len(res.Items))
		for _, item := range res.Items {
			entities = append(entities, item.Role+":"+item.Entity)
		}

		d.Set("role_entity", entities)
	} else {
		d.Set("role_entity", nil)
	}

	return nil
}

func resourceStorageBucketAclUpdate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	bucket := d.Get("bucket").(string)

	if d.HasChange("role_entity") {
		bkt, err := config.clientStorage.Buckets.Get(bucket).Do()
		if err != nil {
			return fmt.Errorf("Error reading bucket %q: %v", bucket, err)
		}

		project := strconv.FormatUint(bkt.ProjectNumber, 10)
		o, n := d.GetChange("role_entity")
		old_re, new_re := o.([]interface{}), n.([]interface{})

		old_re_map := make(map[string]string)
		for _, v := range old_re {
			res, err := getRoleEntityPair(v.(string))

			if err != nil {
				return fmt.Errorf(
					"Old state has malformed Role/Entity pair: %v", err)
			}

			old_re_map[res.Entity] = res.Role
		}

		for _, v := range new_re {
			pair, err := getRoleEntityPair(v.(string))

			bucketAccessControl := &storage.BucketAccessControl{
				Role:   pair.Role,
				Entity: pair.Entity,
			}

			// If the old state is missing this entity, it needs to be inserted
			if _, ok := old_re_map[pair.Entity]; !ok {
				_, err = config.clientStorage.BucketAccessControls.Insert(
					bucket, bucketAccessControl).Do()
			}

			// Now we only store the keys that have to be removed
			delete(old_re_map, pair.Entity)

			if err != nil {
				return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err)
			}
		}

		for entity, role := range old_re_map {
			if entity == fmt.Sprintf("project-owners-%s", project) && role == "OWNER" {
				log.Printf("Skipping %s-%s; not deleting owner ACL.", role, entity)
				continue
			}
			log.Printf("[DEBUG]: removing entity %s", entity)
			err := config.clientStorage.BucketAccessControls.Delete(bucket, entity).Do()

			if err != nil {
				return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err)
			}
		}

		return resourceStorageBucketAclRead(d, meta)
	}

	if d.HasChange("default_acl") {
		default_acl := d.Get("default_acl").(string)

		res, err := config.clientStorage.Buckets.Get(bucket).Do()

		if err != nil {
			return fmt.Errorf("Error reading bucket %s: %v", bucket, err)
		}

		res, err = config.clientStorage.Buckets.Update(bucket,
			res).PredefinedDefaultObjectAcl(default_acl).Do()

		if err != nil {
			return fmt.Errorf("Error updating bucket %s: %v", bucket, err)
		}

		return resourceStorageBucketAclRead(d, meta)
	}

	return nil
}

func resourceStorageBucketAclDelete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	bucket := d.Get("bucket").(string)

	bkt, err := config.clientStorage.Buckets.Get(bucket).Do()
	if err != nil {
		return fmt.Errorf("Error retrieving bucket %q: %v", bucket, err)
	}
	project := strconv.FormatUint(bkt.ProjectNumber, 10)

	re_local := d.Get("role_entity").([]interface{})
	for _, v := range re_local {
		res, err := getRoleEntityPair(v.(string))
		if err != nil {
			return err
		}

		if res.Entity == fmt.Sprintf("project-owners-%s", project) && res.Role == "OWNER" {
			log.Printf("Skipping %s-%s; not deleting owner ACL.", res.Role, res.Entity)
			continue
		}

		log.Printf("[DEBUG]: removing entity %s", res.Entity)

		err = config.clientStorage.BucketAccessControls.Delete(bucket, res.Entity).Do()

		if err != nil {
			return fmt.Errorf("Error deleting entity %s ACL: %s", res.Entity, err)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package getter

import (
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"

	urlhelper "github.com/hashicorp/terraform/helper/url"
)

// GitGetter is a Getter implementation that will download a module from
// a git repository.
type GitGetter struct{}

func (g *GitGetter) Get(dst string, u *url.URL) error {
	if _, err := exec.LookPath("git"); err != nil {
		return fmt.Errorf("git must be available and on the PATH")
	}

	// Extract some query parameters we use
	var ref string
	q := u.Query()
	if len(q) > 0 {
		ref = q.Get("ref")
		q.Del("ref")

		// Copy the URL
		var newU url.URL = *u
		u = &newU
		u.RawQuery = q.Encode()
	}

	// First: clone or update the repository
	_, err := os.Stat(dst)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if err == nil {
		err = g.update(dst, ref)
	} else {
		err = g.clone(dst, u)
	}
	if err != nil {
		return err
	}

	// Next: check out the proper tag/branch if it is specified, and checkout
	if ref == "" {
		return nil
	}

	return g.checkout(dst, ref)
}

// GetFile for Git doesn't support updating at this time. It will download
// the file every time.
func (g *GitGetter) GetFile(dst string, u *url.URL) error {
	td, err := ioutil.TempDir("", "getter-git")
	if err != nil {
		return err
	}
	if err := os.RemoveAll(td); err != nil {
		return err
	}

	// Get the filename, and strip the filename from the URL so we can
	// just get the repository directly.
	filename := filepath.Base(u.Path)
	u.Path = filepath.Dir(u.Path)

	// Get the full repository
	if err := g.Get(td, u); err != nil {
		return err
	}

	// Copy the single file
	u, err = urlhelper.Parse(fmtFileURL(filepath.Join(td, filename)))
	if err != nil {
		return err
	}

	fg := &FileGetter{Copy: true}
	return fg.GetFile(dst, u)
}

func (g *GitGetter) checkout(dst string, ref string) error {
	cmd := exec.Command("git", "checkout", ref)
	cmd.Dir = dst
	return getRunCommand(cmd)
}

func (g *GitGetter) clone(dst string, u *url.URL) error {
	cmd := exec.Command("git", "clone", u.String(), dst)
	return getRunCommand(cmd)
}

func (g *GitGetter) update(dst string, ref string) error {
	// Determine if we're a branch. If we're NOT a branch, then we just
	// switch to master prior to checking out
	cmd := exec.Command("git", "show-ref", "-q", "--verify", "refs/heads/"+ref)
	cmd.Dir = dst
	if getRunCommand(cmd) != nil {
		// Not a branch, switch to master
		ref = "master"
	}

	// We have to be on a branch to pull
	if err := g.checkout(dst, ref); err != nil {
		return err
	}

	cmd = exec.Command("git", "pull", "--ff-only")
	cmd.Dir = dst
	return getRunCommand(cmd)
}
<commit_msg>update comment<commit_after>package getter

import (
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"

	urlhelper "github.com/hashicorp/terraform/helper/url"
)

// GitGetter is a Getter implementation that will download a module from
// a git repository.
type GitGetter struct{}

func (g *GitGetter) Get(dst string, u *url.URL) error {
	if _, err := exec.LookPath("git"); err != nil {
		return fmt.Errorf("git must be available and on the PATH")
	}

	// Extract some query parameters we use
	var ref string
	q := u.Query()
	if len(q) > 0 {
		ref = q.Get("ref")
		q.Del("ref")

		// Copy the URL
		var newU url.URL = *u
		u = &newU
		u.RawQuery = q.Encode()
	}

	// First: clone or update the repository
	_, err := os.Stat(dst)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if err == nil {
		err = g.update(dst, ref)
	} else {
		err = g.clone(dst, u)
	}
	if err != nil {
		return err
	}

	// Next: check out the proper tag/branch if it is specified, and checkout
	if ref == "" {
		return nil
	}

	return g.checkout(dst, ref)
}

// GetFile for Git doesn't support updating at this time. It will download
// the file every time.
func (g *GitGetter) GetFile(dst string, u *url.URL) error {
	td, err := ioutil.TempDir("", "getter-git")
	if err != nil {
		return err
	}
	if err := os.RemoveAll(td); err != nil {
		return err
	}

	// Get the filename, and strip the filename from the URL so we can
	// just get the repository directly.
	filename := filepath.Base(u.Path)
	u.Path = filepath.Dir(u.Path)

	// Get the full repository
	if err := g.Get(td, u); err != nil {
		return err
	}

	// Copy the single file
	u, err = urlhelper.Parse(fmtFileURL(filepath.Join(td, filename)))
	if err != nil {
		return err
	}

	fg := &FileGetter{Copy: true}
	return fg.GetFile(dst, u)
}

func (g *GitGetter) checkout(dst string, ref string) error {
	cmd := exec.Command("git", "checkout", ref)
	cmd.Dir = dst
	return getRunCommand(cmd)
}

func (g *GitGetter) clone(dst string, u *url.URL) error {
	cmd := exec.Command("git", "clone", u.String(), dst)
	return getRunCommand(cmd)
}

func (g *GitGetter) update(dst string, ref string) error {
	// Determine if we're a branch. If we're NOT a branch, then we just
	// switch to master prior to checking out
	cmd := exec.Command("git", "show-ref", "-q", "--verify", "refs/heads/"+ref)
	cmd.Dir = dst
	if getRunCommand(cmd) != nil {
		// Not a branch, switch to master. This will also catch non-existent
		// branches, in which case we want to switch to master and then
		// checkout the proper branch later.
		ref = "master"
	}

	// We have to be on a branch to pull
	if err := g.checkout(dst, ref); err != nil {
		return err
	}

	cmd = exec.Command("git", "pull", "--ff-only")
	cmd.Dir = dst
	return getRunCommand(cmd)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"sync"
	"syscall"
	"time"
)

type Command struct {
	Delay   int
	Timeout int
	Command string
}

type Config struct {
	Workers  int
	Commands []*Command
	disabled bool
}

const CONFIG_WRAPPER = `
workers=5
default_timeout=0
commands=$(jq -n '[]')

command() {
    delay=$1; shift
    commands=$(echo "$commands" | \
        jq --arg delay "$delay" --arg cmd "$*" \
           --arg timeout "${timeout:-$default_timeout}" \
           '. + [{Timeout: ($timeout|tonumber), Delay: ($delay|tonumber), Command: $cmd}]')
    timeout=
}

. %s

echo "$commands" | jq --arg workers "$workers" '{Workers: ($workers|tonumber), Commands: .}'
`

func readConfig() (cfg *Config, err error) {
	var cfgFile string

	if len(os.Args) > 1 {
		cfgFile = os.Args[1]
	} else {
		cfgFile = os.ExpandEnv("$HOME/.config/ggsrc")
	}

	sp := exec.Command("sh")
	sp.Stderr = os.Stderr
	sp.Stdin = bytes.NewBuffer([]byte(fmt.Sprintf(CONFIG_WRAPPER, cfgFile)))
	out, err := sp.Output()
	if err != nil {
		return nil, err
	}

	cfg = new(Config)
	err = json.Unmarshal(out, cfg)
	if err != nil {
		return nil, err
	}

	return cfg, nil
}

func process(cmd *Command) {
	var timer *time.Timer
	var err error

	log.Print(cmd.Command)
	sp := exec.Command("sh", "-c", cmd.Command)
	sp.Stdout = os.Stdout
	sp.Stderr = os.Stderr
	if err = sp.Start(); err != nil {
		log.Printf("%s failed: %s", err.Error(), cmd.Command)
		return
	}
	if cmd.Timeout > 0 {
		timer = time.AfterFunc(time.Duration(cmd.Timeout)*time.Second, func() {
			timer = nil
			if sp.ProcessState == nil {
				sp.Process.Kill()
			}
		})
	}
	err = sp.Wait()
	if timer != nil {
		timer.Stop()
	}
	if err != nil {
		log.Printf("%s failed: %s", err.Error(), cmd.Command)
	}
}

func reload(oldConfig *Config) (config *Config, err error) {
	var wg sync.WaitGroup
	var once sync.Once

	config, err = readConfig()
	if err != nil {
		return nil, err
	}

	ch := make(chan *Command, len(config.Commands))

	for i := 0; i < config.Workers; i++ {
		go func() {
			wg.Add(1)

			for !config.disabled {
				cmd := <-ch
				process(cmd)
				wg.Add(1)
				time.AfterFunc(time.Duration(cmd.Delay)*time.Second, func() {
					ch <- cmd
					wg.Done()
				})
			}

			wg.Done()
			wg.Wait()
			once.Do(func() { close(ch) })
		}()
	}

	for _, cmd := range config.Commands {
		ch <- cmd
	}

	if oldConfig != nil {
		oldConfig.disabled = true
	}

	return config, nil
}

func main() {
	config, err := reload(nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while reading configuration: %s", err)
		os.Exit(1)
	}

	// wait for signals (interrupt, reload)
	sigChan := make(chan os.Signal, 2)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGUSR1)
	for sig := range sigChan {
		switch sig {
		case syscall.SIGINT:
			return
		case syscall.SIGUSR1:
			config, err = reload(config)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Error while reloading configuration: %s", err)
			}
		}
	}
}
<commit_msg>ggs: better logging & timeout handling<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"sync"
	"syscall"
	"time"
)

type Command struct {
	Delay   int
	Timeout int
	Command string
}

type Config struct {
	Workers  int
	Commands []*Command
	disabled bool
}

const CONFIG_WRAPPER = `
workers=5
default_timeout=0
commands=$(jq -n '[]')

command() {
    delay=$1; shift
    commands=$(echo "$commands" | \
        jq --arg delay "$delay" --arg cmd "$*" \
           --arg timeout "${timeout:-$default_timeout}" \
           '. + [{Timeout: ($timeout|tonumber), Delay: ($delay|tonumber), Command: $cmd}]')
    timeout=
}

. %s

echo "$commands" | jq --arg workers "$workers" '{Workers: ($workers|tonumber), Commands: .}'
`

func readConfig() (cfg *Config, err error) {
	var cfgFile string

	if len(os.Args) > 1 {
		cfgFile = os.Args[1]
	} else {
		cfgFile = os.ExpandEnv("$HOME/.config/ggsrc")
	}

	sp := exec.Command("sh")
	sp.Stderr = os.Stderr
	sp.Stdin = bytes.NewBuffer([]byte(fmt.Sprintf(CONFIG_WRAPPER, cfgFile)))
	out, err := sp.Output()
	if err != nil {
		return nil, err
	}

	cfg = new(Config)
	err = json.Unmarshal(out, cfg)
	if err != nil {
		return nil, err
	}

	return cfg, nil
}

func process(cmd *Command) {
	var timer *time.Timer
	var err error

	sp := exec.Command("sh", "-c", cmd.Command)
	sp.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
	sp.Stdout = os.Stdout
	sp.Stderr = os.Stderr
	if err = sp.Start(); err != nil {
		log.Printf("%s failed: %s", err.Error(), cmd.Command)
		return
	}
	log.Printf("(%d) %s", sp.Process.Pid, cmd.Command)
	if cmd.Timeout > 0 {
		timer = time.AfterFunc(time.Duration(cmd.Timeout)*time.Second, func() {
			if sp.ProcessState == nil {
				syscall.Kill(-sp.Process.Pid, syscall.SIGTERM)
			}
		})
	}
	if err = sp.Wait(); err != nil {
		log.Printf("(%d) %s failed: %s", sp.Process.Pid, cmd.Command, err.Error())
	} else {
		log.Printf("(%d) done", sp.Process.Pid)
	}
	timer.Stop()
}

func reload(oldConfig *Config) (config *Config, err error) {
	var wg sync.WaitGroup
	var once sync.Once

	config, err = readConfig()
	if err != nil {
		return nil, err
	}

	ch := make(chan *Command, len(config.Commands))

	for i := 0; i < config.Workers; i++ {
		go func() {
			wg.Add(1)

			for !config.disabled {
				cmd := <-ch
				process(cmd)
				wg.Add(1)
				time.AfterFunc(time.Duration(cmd.Delay)*time.Second, func() {
					ch <- cmd
					wg.Done()
				})
			}

			wg.Done()
			wg.Wait()
			once.Do(func() { close(ch) })
		}()
	}

	for _, cmd := range config.Commands {
		ch <- cmd
	}

	if oldConfig != nil {
		oldConfig.disabled = true
	}

	return config, nil
}

func main() {
	config, err := reload(nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error while reading configuration: %s", err)
		os.Exit(1)
	}

	// wait for signals (interrupt, reload)
	sigChan := make(chan os.Signal, 2)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGUSR1)
	for sig := range sigChan {
		switch sig {
		case syscall.SIGINT:
			return
		case syscall.SIGUSR1:
			config, err = reload(config)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Error while reloading configuration: %s", err)
			}
		}
	}
}
<|endoftext|>


<commit_before>package git // import "sevki.org/joker/git"

import (
	"bufio"
	"bytes"

	"github.com/google/go-github/github"
	"github.com/sourcegraph/go-diff/diff"
)

// ChangeSet represents a set of files that have changed.
type ChangeSet []github.CommitFile

// DiffLine returns the corresponding DiffLine
func DiffLine(bz []byte, s, l int) int {
	buf := bytes.NewBuffer(bz)
	n := s - 1
	t := 0
	scanner := bufio.NewScanner(buf)
	for scanner.Scan() {
		q := string(scanner.Text())
		switch q[0] {
		case '-':
			break
		default:
			n++
		}
		t++
		// new file and diff line same
		if n == l {
			if q[0] == ' ' {
				return -1
			}
			return t
		}
	}
	return -2
}

// LineNumFromDiff returns the line number corresponding to the  Diff line
func LineNumFromDiff(bz []byte, s, l int) int {
	buf := bytes.NewBuffer(bz)
	n := s
	t := 0
	scanner := bufio.NewScanner(buf)
	for scanner.Scan() {
		q := string(scanner.Text())
		switch q[0] {
		case '-':
			break
		default:
			n++
		}
		t++
		// new file and diff line same
		if n == l {
			if q[0] == ' ' {
				return -1
			}
			return n
		}
	}
	return -2
}

// ChangedFiles returned as a string array
func ChangedFiles(fs []github.CommitFile) (changeset []string) {
	for _, f := range fs {
		changeset = append(changeset, *f.Filename)
	}
	return
}

// LineIsNew returns true if the line
func LineIsNew(commit *github.RepositoryCommit, l int, f string) int {

	// Check IF the file has changed
	for _, k := range commit.Files {

		if *k.Filename == f {
			// Parse the file patch
			pdiff, _ := diff.ParseHunks([]byte(*k.Patch))

			for _, i := range pdiff {
				return DiffLine(i.Body, i.NewStartLine, l)
			}
		}
	}
	return -1
}
<commit_msg>canonical path<commit_after>package git // import "sevki.org/joker/git"

import (
	"bufio"
	"bytes"

	"github.com/google/go-github/github"
	"sourcegraph.com/sourcegraph/go-diff/diff"
)

// ChangeSet represents a set of files that have changed.
type ChangeSet []github.CommitFile

// DiffLine returns the corresponding DiffLine
func DiffLine(bz []byte, s, l int) int {
	buf := bytes.NewBuffer(bz)
	n := s - 1
	t := 0
	scanner := bufio.NewScanner(buf)
	for scanner.Scan() {
		q := string(scanner.Text())
		switch q[0] {
		case '-':
			break
		default:
			n++
		}
		t++
		// new file and diff line same
		if n == l {
			if q[0] == ' ' {
				return -1
			}
			return t
		}
	}
	return -2
}

// LineNumFromDiff returns the line number corresponding to the  Diff line
func LineNumFromDiff(bz []byte, s, l int) int {
	buf := bytes.NewBuffer(bz)
	n := s
	t := 0
	scanner := bufio.NewScanner(buf)
	for scanner.Scan() {
		q := string(scanner.Text())
		switch q[0] {
		case '-':
			break
		default:
			n++
		}
		t++
		// new file and diff line same
		if n == l {
			if q[0] == ' ' {
				return -1
			}
			return n
		}
	}
	return -2
}

// ChangedFiles returned as a string array
func ChangedFiles(fs []github.CommitFile) (changeset []string) {
	for _, f := range fs {
		changeset = append(changeset, *f.Filename)
	}
	return
}

// LineIsNew returns true if the line
func LineIsNew(commit *github.RepositoryCommit, l int, f string) int {

	// Check IF the file has changed
	for _, k := range commit.Files {

		if *k.Filename == f {
			// Parse the file patch
			pdiff, _ := diff.ParseHunks([]byte(*k.Patch))

			for _, i := range pdiff {
				return DiffLine(i.Body, i.NewStartLine, l)
			}
		}
	}
	return -1
}
<|endoftext|>


<commit_before>package git

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/github/hub/cmd"
)

var GlobalFlags []string

func Version() (string, error) {
	output, err := gitOutput("version")
	if err != nil {
		return "", fmt.Errorf("Can't load git version")
	}

	return output[0], nil
}

func Dir() (string, error) {
	output, err := gitOutput("rev-parse", "-q", "--git-dir")
	if err != nil {
		return "", fmt.Errorf("Not a git repository (or any of the parent directories): .git")
	}

	gitDir := output[0]
	gitDir, err = filepath.Abs(gitDir)
	if err != nil {
		return "", err
	}

	return gitDir, nil
}

func HasFile(segments ...string) bool {
	// The blessed way to resolve paths within git dir since Git 2.5.0
	output, err := gitOutput("rev-parse", "-q", "--git-path", filepath.Join(segments...))
	if err == nil {
		if _, err := os.Stat(output[0]); err == nil {
			return true
		}
	}

	// Fallback for older git versions
	dir, err := Dir()
	if err != nil {
		return false
	}

	s := []string{dir}
	s = append(s, segments...)
	path := filepath.Join(s...)
	if _, err := os.Stat(path); err == nil {
		return true
	}

	return false
}

func BranchAtRef(paths ...string) (name string, err error) {
	dir, err := Dir()
	if err != nil {
		return
	}

	segments := []string{dir}
	segments = append(segments, paths...)
	path := filepath.Join(segments...)
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}

	n := string(b)
	refPrefix := "ref: "
	if strings.HasPrefix(n, refPrefix) {
		name = strings.TrimPrefix(n, refPrefix)
		name = strings.TrimSpace(name)
	} else {
		err = fmt.Errorf("No branch info in %s: %s", path, n)
	}

	return
}

func Editor() (string, error) {
	output, err := gitOutput("var", "GIT_EDITOR")
	if err != nil {
		return "", fmt.Errorf("Can't load git var: GIT_EDITOR")
	}

	return output[0], nil
}

func Head() (string, error) {
	return BranchAtRef("HEAD")
}

func SymbolicFullName(name string) (string, error) {
	output, err := gitOutput("rev-parse", "--symbolic-full-name", name)
	if err != nil {
		return "", fmt.Errorf("Unknown revision or path not in the working tree: %s", name)
	}

	return output[0], nil
}

func Ref(ref string) (string, error) {
	output, err := gitOutput("rev-parse", "-q", ref)
	if err != nil {
		return "", fmt.Errorf("Unknown revision or path not in the working tree: %s", ref)
	}

	return output[0], nil
}

func RefList(a, b string) ([]string, error) {
	ref := fmt.Sprintf("%s...%s", a, b)
	output, err := gitOutput("rev-list", "--cherry-pick", "--right-only", "--no-merges", ref)
	if err != nil {
		return []string{}, fmt.Errorf("Can't load rev-list for %s", ref)
	}

	return output, nil
}

func CommentChar() string {
	char, err := Config("core.commentchar")
	if err != nil {
		char = "#"
	}

	return char
}

func Show(sha string) (string, error) {
	cmd := cmd.New("git")
	cmd.WithArg("show").WithArg("-s").WithArg("--format=%s%n%+b").WithArg(sha)

	output, err := cmd.CombinedOutput()
	output = strings.TrimSpace(output)

	return output, err
}

func Log(sha1, sha2 string) (string, error) {
	execCmd := cmd.New("git")
	execCmd.WithArg("log").WithArg("--no-color")
	execCmd.WithArg("--format=%h (%aN, %ar)%n%w(78,3,3)%s%n%+b")
	execCmd.WithArg("--cherry")
	shaRange := fmt.Sprintf("%s...%s", sha1, sha2)
	execCmd.WithArg(shaRange)

	outputs, err := execCmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("Can't load git log %s..%s", sha1, sha2)
	}

	return outputs, nil
}

func Remotes() ([]string, error) {
	return gitOutput("remote", "-v")
}

func Config(name string) (string, error) {
	return gitGetConfig(name)
}

func GlobalConfig(name string) (string, error) {
	return gitGetConfig("--global", name)
}

func SetGlobalConfig(name, value string) error {
	_, err := gitConfig("--global", name, value)
	return err
}

func gitGetConfig(args ...string) (string, error) {
	output, err := gitOutput(gitConfigCommand(args)...)
	if err != nil {
		return "", fmt.Errorf("Unknown config %s", args[len(args)-1])
	}

	if len(output) == 0 {
		return "", nil
	}

	return output[0], nil
}

func gitConfig(args ...string) ([]string, error) {
	return gitOutput(gitConfigCommand(args)...)
}

func gitConfigCommand(args []string) []string {
	cmd := []string{"config"}
	return append(cmd, args...)
}

func Alias(name string) (string, error) {
	return Config(fmt.Sprintf("alias.%s", name))
}

func Run(command string, args ...string) error {
	cmd := cmd.New("git")

	for _, v := range GlobalFlags {
		cmd.WithArg(v)
	}

	cmd.WithArg(command)

	for _, a := range args {
		cmd.WithArg(a)
	}

	return cmd.Run()
}

func gitOutput(input ...string) (outputs []string, err error) {
	cmd := cmd.New("git")

	for _, v := range GlobalFlags {
		cmd.WithArg(v)
	}

	for _, i := range input {
		cmd.WithArg(i)
	}

	out, err := cmd.CombinedOutput()
	for _, line := range strings.Split(out, "\n") {
		line = strings.TrimSpace(line)
		if line != "" {
			outputs = append(outputs, string(line))
		}
	}

	return outputs, err
}
<commit_msg>Detect `git rev-parse` output when on git version < 2.5<commit_after>package git

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/github/hub/cmd"
)

var GlobalFlags []string

func Version() (string, error) {
	output, err := gitOutput("version")
	if err != nil {
		return "", fmt.Errorf("Can't load git version")
	}

	return output[0], nil
}

func Dir() (string, error) {
	output, err := gitOutput("rev-parse", "-q", "--git-dir")
	if err != nil {
		return "", fmt.Errorf("Not a git repository (or any of the parent directories): .git")
	}

	gitDir := output[0]
	gitDir, err = filepath.Abs(gitDir)
	if err != nil {
		return "", err
	}

	return gitDir, nil
}

func HasFile(segments ...string) bool {
	// The blessed way to resolve paths within git dir since Git 2.5.0
	output, err := gitOutput("rev-parse", "-q", "--git-path", filepath.Join(segments...))
	if err == nil && output[0] != "--git-path" {
		if _, err := os.Stat(output[0]); err == nil {
			return true
		}
	}

	// Fallback for older git versions
	dir, err := Dir()
	if err != nil {
		return false
	}

	s := []string{dir}
	s = append(s, segments...)
	path := filepath.Join(s...)
	if _, err := os.Stat(path); err == nil {
		return true
	}

	return false
}

func BranchAtRef(paths ...string) (name string, err error) {
	dir, err := Dir()
	if err != nil {
		return
	}

	segments := []string{dir}
	segments = append(segments, paths...)
	path := filepath.Join(segments...)
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}

	n := string(b)
	refPrefix := "ref: "
	if strings.HasPrefix(n, refPrefix) {
		name = strings.TrimPrefix(n, refPrefix)
		name = strings.TrimSpace(name)
	} else {
		err = fmt.Errorf("No branch info in %s: %s", path, n)
	}

	return
}

func Editor() (string, error) {
	output, err := gitOutput("var", "GIT_EDITOR")
	if err != nil {
		return "", fmt.Errorf("Can't load git var: GIT_EDITOR")
	}

	return output[0], nil
}

func Head() (string, error) {
	return BranchAtRef("HEAD")
}

func SymbolicFullName(name string) (string, error) {
	output, err := gitOutput("rev-parse", "--symbolic-full-name", name)
	if err != nil {
		return "", fmt.Errorf("Unknown revision or path not in the working tree: %s", name)
	}

	return output[0], nil
}

func Ref(ref string) (string, error) {
	output, err := gitOutput("rev-parse", "-q", ref)
	if err != nil {
		return "", fmt.Errorf("Unknown revision or path not in the working tree: %s", ref)
	}

	return output[0], nil
}

func RefList(a, b string) ([]string, error) {
	ref := fmt.Sprintf("%s...%s", a, b)
	output, err := gitOutput("rev-list", "--cherry-pick", "--right-only", "--no-merges", ref)
	if err != nil {
		return []string{}, fmt.Errorf("Can't load rev-list for %s", ref)
	}

	return output, nil
}

func CommentChar() string {
	char, err := Config("core.commentchar")
	if err != nil {
		char = "#"
	}

	return char
}

func Show(sha string) (string, error) {
	cmd := cmd.New("git")
	cmd.WithArg("show").WithArg("-s").WithArg("--format=%s%n%+b").WithArg(sha)

	output, err := cmd.CombinedOutput()
	output = strings.TrimSpace(output)

	return output, err
}

func Log(sha1, sha2 string) (string, error) {
	execCmd := cmd.New("git")
	execCmd.WithArg("log").WithArg("--no-color")
	execCmd.WithArg("--format=%h (%aN, %ar)%n%w(78,3,3)%s%n%+b")
	execCmd.WithArg("--cherry")
	shaRange := fmt.Sprintf("%s...%s", sha1, sha2)
	execCmd.WithArg(shaRange)

	outputs, err := execCmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("Can't load git log %s..%s", sha1, sha2)
	}

	return outputs, nil
}

func Remotes() ([]string, error) {
	return gitOutput("remote", "-v")
}

func Config(name string) (string, error) {
	return gitGetConfig(name)
}

func GlobalConfig(name string) (string, error) {
	return gitGetConfig("--global", name)
}

func SetGlobalConfig(name, value string) error {
	_, err := gitConfig("--global", name, value)
	return err
}

func gitGetConfig(args ...string) (string, error) {
	output, err := gitOutput(gitConfigCommand(args)...)
	if err != nil {
		return "", fmt.Errorf("Unknown config %s", args[len(args)-1])
	}

	if len(output) == 0 {
		return "", nil
	}

	return output[0], nil
}

func gitConfig(args ...string) ([]string, error) {
	return gitOutput(gitConfigCommand(args)...)
}

func gitConfigCommand(args []string) []string {
	cmd := []string{"config"}
	return append(cmd, args...)
}

func Alias(name string) (string, error) {
	return Config(fmt.Sprintf("alias.%s", name))
}

func Run(command string, args ...string) error {
	cmd := cmd.New("git")

	for _, v := range GlobalFlags {
		cmd.WithArg(v)
	}

	cmd.WithArg(command)

	for _, a := range args {
		cmd.WithArg(a)
	}

	return cmd.Run()
}

func gitOutput(input ...string) (outputs []string, err error) {
	cmd := cmd.New("git")

	for _, v := range GlobalFlags {
		cmd.WithArg(v)
	}

	for _, i := range input {
		cmd.WithArg(i)
	}

	out, err := cmd.CombinedOutput()
	for _, line := range strings.Split(out, "\n") {
		line = strings.TrimSpace(line)
		if line != "" {
			outputs = append(outputs, string(line))
		}
	}

	return outputs, err
}
<|endoftext|>


<commit_before>package sender

import (
	"socialapi/workers/common/runner"
	"socialapi/workers/email/emailmodels"
	"socialapi/workers/email/privatemessageemail/common"
	"socialapi/workers/email/privatemessageemail/testhelper"
	"socialapi/workers/helper"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestChatEmailSender(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldn't start bongo %s", err.Error())
	}
	defer r.Close()

	redisConf := r.Conf
	redisConn := helper.MustInitRedisConn(redisConf)
	defer redisConn.Close()

	controller, _ := New(redisConn, r.Log, &emailmodels.EmailSettings{}, r.Metrics)

	Convey("while fetching a message from pending notification queue", t, func() {
		testPeriod := "1"
		accountId := "12"
		channelId := "5938384687973007478"
		awaySince := "1257894000000000000"
		err := redisConn.HashMultipleSet(
			common.AccountNextPeriodHashSetKey(),
			map[string]interface{}{accountId: testPeriod},
		)
		So(err, ShouldBeNil)

		_, err = redisConn.AddSetMembers(common.PeriodAccountSetKey(testPeriod), accountId)
		So(err, ShouldBeNil)

		err = redisConn.HashMultipleSet(
			common.AccountChannelHashSetKey(12),
			map[string]interface{}{channelId: awaySince},
		)
		So(err, ShouldBeNil)

		Convey("should be able to get next account from set", func() {
			account, err := controller.NextAccount(testPeriod)
			So(err, ShouldBeNil)
			So(account.Id, ShouldEqual, 12)

			length, err := redisConn.Scard(common.PeriodAccountSetKey(testPeriod))
			So(err, ShouldBeNil)
			So(length, ShouldEqual, 0)

			length, err = redisConn.GetHashLength(common.AccountNextPeriodHashSetKey())
			So(err, ShouldBeNil)
			So(length, ShouldEqual, 0)
		})
		Reset(func() {
			testhelper.ResetCache(redisConn)
		})
	})

}
<commit_msg>pritemessagesender/tests: EmailSettings is removed<commit_after>package sender

import (
	"socialapi/workers/common/runner"
	"socialapi/workers/email/privatemessageemail/common"
	"socialapi/workers/email/privatemessageemail/testhelper"
	"socialapi/workers/helper"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
)

func TestChatEmailSender(t *testing.T) {
	r := runner.New("test")
	if err := r.Init(); err != nil {
		t.Fatalf("couldn't start bongo %s", err.Error())
	}
	defer r.Close()

	redisConf := r.Conf
	redisConn := helper.MustInitRedisConn(redisConf)
	defer redisConn.Close()

	controller, _ := New(redisConn, r.Log, r.Metrics)

	Convey("while fetching a message from pending notification queue", t, func() {
		testPeriod := "1"
		accountId := "12"
		channelId := "5938384687973007478"
		awaySince := "1257894000000000000"
		err := redisConn.HashMultipleSet(
			common.AccountNextPeriodHashSetKey(),
			map[string]interface{}{accountId: testPeriod},
		)
		So(err, ShouldBeNil)

		_, err = redisConn.AddSetMembers(common.PeriodAccountSetKey(testPeriod), accountId)
		So(err, ShouldBeNil)

		err = redisConn.HashMultipleSet(
			common.AccountChannelHashSetKey(12),
			map[string]interface{}{channelId: awaySince},
		)
		So(err, ShouldBeNil)

		Convey("should be able to get next account from set", func() {
			account, err := controller.NextAccount(testPeriod)
			So(err, ShouldBeNil)
			So(account.Id, ShouldEqual, 12)

			length, err := redisConn.Scard(common.PeriodAccountSetKey(testPeriod))
			So(err, ShouldBeNil)
			So(length, ShouldEqual, 0)

			length, err = redisConn.GetHashLength(common.AccountNextPeriodHashSetKey())
			So(err, ShouldBeNil)
			So(length, ShouldEqual, 0)
		})
		Reset(func() {
			testhelper.ResetCache(redisConn)
		})
	})

}
<|endoftext|>


<commit_before>// +build linux freebsd openbsd darwin solaris illumos

package client // import "github.com/docker/docker/client"

// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
const DefaultDockerHost = "unix:///var/run/docker.sock"

const defaultProto = "unix"
const defaultAddr = "/var/run/docker.sock"
<commit_msg>Enable client on netbsd and dragonfly<commit_after>// +build linux freebsd openbsd netbsd darwin solaris illumos dragonfly

package client // import "github.com/docker/docker/client"

// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
const DefaultDockerHost = "unix:///var/run/docker.sock"

const defaultProto = "unix"
const defaultAddr = "/var/run/docker.sock"
<|endoftext|>


<commit_before>// Package gmetric provides a client for the ganglia gmetric API.
package gmetric

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"time"
)

var zeroByte = []byte{byte(0)}

type slopeType uint32

// The slope types supported by Ganglia.
const (
	SlopeZero slopeType = iota
	SlopePositive
	SlopeNegative
	SlopeBoth
	SlopeUnspecified
)

type valueType uint

// The value types supported by Ganglia.
const (
	ValueString valueType = iota + 1
	ValueUint8
	ValueInt8
	ValueUint16
	ValueInt16
	ValueUint32
	ValueInt32
	ValueFloat32
	ValueFloat64
)

// Type string per configured type.
func (v valueType) Type() string {
	switch v {
	case ValueString:
		return "string"
	case ValueUint8:
		return "uint8"
	case ValueInt8:
		return "int8"
	case ValueUint16:
		return "uint16"
	case ValueInt16:
		return "int16"
	case ValueUint32:
		return "uint32"
	case ValueInt32:
		return "int32"
	case ValueFloat32:
		return "float"
	case ValueFloat64:
		return "double"
	}
	return "unknown"
}

// Encode a value.
func (v valueType) encode(w io.Writer, val interface{}) error {
	switch v {
	default:
		writeString(w, fmt.Sprint(val))
	case ValueUint8, ValueInt8, ValueUint16, ValueInt16, ValueUint32, ValueInt32:
		writeString(w, fmt.Sprintf("%d", val))
	case ValueFloat32, ValueFloat64:
		writeString(w, fmt.Sprintf("%f", val))
	}
	return nil
}

// Represents a collection of errors.
type MultiError []error

// Returns a concatenation of all the contained errors.
func (m MultiError) Error() string {
	var buf bytes.Buffer
	buf.WriteString("gmetric: multi-error:")
	for _, e := range m {
		buf.WriteRune('\n')
		buf.WriteString(e.Error())
	}
	return buf.String()
}

// A Client represents a set of connections to write metrics to.
type Client struct {
	Addr   []*net.UDPAddr
	conn   []*net.UDPConn
	writer io.Writer
}

// Defines a Metric.
type Metric struct {
	Name         string
	Title        string
	Description  string
	Group        string
	Units        string
	Host         string
	Spoof        string
	ValueType    valueType
	Slope        slopeType
	TickInterval time.Duration // Also known as TMax.
	Lifetime     time.Duration // Also known as DMax.
}

// Writes a metadata packet for the Metric.
func (m *Metric) EncodeMeta(w io.Writer) error {
	writeUint32(w, 128)
	m.writeHead(w)
	writeString(w, m.ValueType.Type())
	writeString(w, m.Name)
	writeString(w, m.Units)
	writeUint32(w, uint32(m.Slope))
	writeUint32(w, uint32(m.TickInterval.Seconds()))
	writeUint32(w, uint32(m.Lifetime.Seconds()))

	var pairs [][2]string
	if m.Title != "" {
		pairs = append(pairs, [2]string{"TITLE", m.Title})
	}
	if m.Description != "" {
		pairs = append(pairs, [2]string{"DESC", m.Description})
	}
	if m.Spoof != "" {
		pairs = append(pairs, [2]string{"SPOOF_HOST", m.Spoof})
	}
	if m.Group != "" {
		pairs = append(pairs, [2]string{"GROUP", m.Group})
	}
	writePairs(w, pairs)
	return nil
}

// Writes a value packet for the given value. The value will be encoded based
// on the configured ValueType.
func (m *Metric) EncodeValue(w io.Writer, val interface{}) error {
	writeUint32(w, 133)
	m.writeHead(w)
	writeString(w, "%s")
	return m.ValueType.encode(w, val)
}

func (m *Metric) writeHead(w io.Writer) {
	spoof := m.Spoof != ""
	if spoof {
		writeString(w, m.Spoof)
	} else {
		writeString(w, m.Host)
	}
	writeString(w, m.Name)
	if spoof {
		writeUint32(w, 1)
	} else {
		writeUint32(w, 0)
	}
}

// Send the Metric metadata.
func (c *Client) SendMeta(m *Metric) error {
	var buf bytes.Buffer
	if err := m.EncodeMeta(&buf); err != nil {
		return err
	}
	c.writer.Write(buf.Bytes())
	return nil
}

// Send a value for the Metric.
func (c *Client) SendValue(m *Metric, val interface{}) error {
	var buf bytes.Buffer
	if err := m.EncodeValue(&buf, val); err != nil {
		return err
	}
	c.writer.Write(buf.Bytes())
	return nil
}

// Start the client and establish the connections. If an error is returned it
// will be a MultiError.
func (c *Client) Start() error {
	var errs MultiError
	var writers []io.Writer
	for _, addr := range c.Addr {
		s, err := net.DialUDP("udp", nil, addr)
		if err != nil {
			errs = append(errs, err)
			continue
		}
		c.conn = append(c.conn, s)
		writers = append(writers, s)
	}
	c.writer = io.MultiWriter(writers...)

	if len(errs) == 0 {
		return nil
	}
	return errs
}

// Shutdown the client and close the connections. If an error is returned it
// will be a MultiError.
func (c *Client) Stop() error {
	var errs MultiError
	for _, conn := range c.conn {
		if err := conn.Close(); err != nil {
			errs = append(errs, err)
		}
	}

	if len(errs) == 0 {
		return nil
	}
	return errs
}

func writeUint32(w io.Writer, val uint32) {
	w.Write([]byte{
		byte(val >> 24 & 0xff),
		byte(val >> 16 & 0xff),
		byte(val >> 8 & 0xff),
		byte(val & 0xff),
	})
}

func writeString(w io.Writer, val string) {
	l := uint32(len(val))
	writeUint32(w, l)
	fmt.Fprint(w, val)
	offset := l % 4
	if offset != 0 {
		for j := offset; j < 4; j++ {
			w.Write(zeroByte)
		}
	}
}

func writePairs(w io.Writer, pairs [][2]string) {
	writeUint32(w, uint32(len(pairs)))
	for _, p := range pairs {
		writeString(w, p[0])
		writeString(w, p[1])
	}
}
<commit_msg>remove unnecessary error return<commit_after>// Package gmetric provides a client for the ganglia gmetric API.
package gmetric

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"time"
)

var zeroByte = []byte{byte(0)}

type slopeType uint32

// The slope types supported by Ganglia.
const (
	SlopeZero slopeType = iota
	SlopePositive
	SlopeNegative
	SlopeBoth
	SlopeUnspecified
)

type valueType uint

// The value types supported by Ganglia.
const (
	ValueString valueType = iota + 1
	ValueUint8
	ValueInt8
	ValueUint16
	ValueInt16
	ValueUint32
	ValueInt32
	ValueFloat32
	ValueFloat64
)

// Type string per configured type.
func (v valueType) Type() string {
	switch v {
	case ValueString:
		return "string"
	case ValueUint8:
		return "uint8"
	case ValueInt8:
		return "int8"
	case ValueUint16:
		return "uint16"
	case ValueInt16:
		return "int16"
	case ValueUint32:
		return "uint32"
	case ValueInt32:
		return "int32"
	case ValueFloat32:
		return "float"
	case ValueFloat64:
		return "double"
	}
	return "unknown"
}

// Encode a value.
func (v valueType) encode(w io.Writer, val interface{}) {
	switch v {
	default:
		writeString(w, fmt.Sprint(val))
	case ValueUint8, ValueInt8, ValueUint16, ValueInt16, ValueUint32, ValueInt32:
		writeString(w, fmt.Sprintf("%d", val))
	case ValueFloat32, ValueFloat64:
		writeString(w, fmt.Sprintf("%f", val))
	}
	panic("not reached")
}

// Represents a collection of errors.
type MultiError []error

// Returns a concatenation of all the contained errors.
func (m MultiError) Error() string {
	var buf bytes.Buffer
	buf.WriteString("gmetric: multi-error:")
	for _, e := range m {
		buf.WriteRune('\n')
		buf.WriteString(e.Error())
	}
	return buf.String()
}

// A Client represents a set of connections to write metrics to.
type Client struct {
	Addr   []*net.UDPAddr
	conn   []*net.UDPConn
	writer io.Writer
}

// Defines a Metric.
type Metric struct {
	Name         string
	Title        string
	Description  string
	Group        string
	Units        string
	Host         string
	Spoof        string
	ValueType    valueType
	Slope        slopeType
	TickInterval time.Duration // Also known as TMax.
	Lifetime     time.Duration // Also known as DMax.
}

// Writes a metadata packet for the Metric.
func (m *Metric) EncodeMeta(w io.Writer) error {
	writeUint32(w, 128)
	m.writeHead(w)
	writeString(w, m.ValueType.Type())
	writeString(w, m.Name)
	writeString(w, m.Units)
	writeUint32(w, uint32(m.Slope))
	writeUint32(w, uint32(m.TickInterval.Seconds()))
	writeUint32(w, uint32(m.Lifetime.Seconds()))

	var pairs [][2]string
	if m.Title != "" {
		pairs = append(pairs, [2]string{"TITLE", m.Title})
	}
	if m.Description != "" {
		pairs = append(pairs, [2]string{"DESC", m.Description})
	}
	if m.Spoof != "" {
		pairs = append(pairs, [2]string{"SPOOF_HOST", m.Spoof})
	}
	if m.Group != "" {
		pairs = append(pairs, [2]string{"GROUP", m.Group})
	}
	writePairs(w, pairs)
	return nil
}

// Writes a value packet for the given value. The value will be encoded based
// on the configured ValueType.
func (m *Metric) EncodeValue(w io.Writer, val interface{}) error {
	writeUint32(w, 133)
	m.writeHead(w)
	writeString(w, "%s")
	m.ValueType.encode(w, val)
	return nil
}

func (m *Metric) writeHead(w io.Writer) {
	spoof := m.Spoof != ""
	if spoof {
		writeString(w, m.Spoof)
	} else {
		writeString(w, m.Host)
	}
	writeString(w, m.Name)
	if spoof {
		writeUint32(w, 1)
	} else {
		writeUint32(w, 0)
	}
}

// Send the Metric metadata.
func (c *Client) SendMeta(m *Metric) error {
	var buf bytes.Buffer
	if err := m.EncodeMeta(&buf); err != nil {
		return err
	}
	c.writer.Write(buf.Bytes())
	return nil
}

// Send a value for the Metric.
func (c *Client) SendValue(m *Metric, val interface{}) error {
	var buf bytes.Buffer
	if err := m.EncodeValue(&buf, val); err != nil {
		return err
	}
	c.writer.Write(buf.Bytes())
	return nil
}

// Start the client and establish the connections. If an error is returned it
// will be a MultiError.
func (c *Client) Start() error {
	var errs MultiError
	var writers []io.Writer
	for _, addr := range c.Addr {
		s, err := net.DialUDP("udp", nil, addr)
		if err != nil {
			errs = append(errs, err)
			continue
		}
		c.conn = append(c.conn, s)
		writers = append(writers, s)
	}
	c.writer = io.MultiWriter(writers...)

	if len(errs) == 0 {
		return nil
	}
	return errs
}

// Shutdown the client and close the connections. If an error is returned it
// will be a MultiError.
func (c *Client) Stop() error {
	var errs MultiError
	for _, conn := range c.conn {
		if err := conn.Close(); err != nil {
			errs = append(errs, err)
		}
	}

	if len(errs) == 0 {
		return nil
	}
	return errs
}

func writeUint32(w io.Writer, val uint32) {
	w.Write([]byte{
		byte(val >> 24 & 0xff),
		byte(val >> 16 & 0xff),
		byte(val >> 8 & 0xff),
		byte(val & 0xff),
	})
}

func writeString(w io.Writer, val string) {
	l := uint32(len(val))
	writeUint32(w, l)
	fmt.Fprint(w, val)
	offset := l % 4
	if offset != 0 {
		for j := offset; j < 4; j++ {
			w.Write(zeroByte)
		}
	}
}

func writePairs(w io.Writer, pairs [][2]string) {
	writeUint32(w, uint32(len(pairs)))
	for _, p := range pairs {
		writeString(w, p[0])
		writeString(w, p[1])
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 Ajit Yagaty
//
// 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 client

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"

	"github.com/ajityagaty/go-kairosdb/builder"
	"github.com/ajityagaty/go-kairosdb/response"
)

var (
	api_version      = "/api/v1"
	datapoints_ep    = api_version + "/datapoints"
	query_ep         = api_version + "/datapoints/query"
	version_ep       = api_version + "/version"
	health_ep        = api_version + "/health/check"
	metricnames_ep   = api_version + "/metricnames"
	tagnames_ep      = api_version + "/tagnames"
	tagvalues_ep     = api_version + "/tagvalues"
	delmetric_ep     = api_version + "/metric/"
	deldatapoints_ep = api_version + "/datapoints/delete"
)

// This is the type that implements the Client interface.
type httpClient struct {
	serverAddress string
}

func NewHttpClient(serverAddress string) Client {
	return &httpClient{
		serverAddress: serverAddress,
	}
}

// Returns a list of all metrics names.
func (hc *httpClient) GetMetricNames() (*response.GetResponse, error) {
	return hc.get(hc.serverAddress + metricnames_ep)
}

// Returns a list of all tag names.
func (hc *httpClient) GetTagNames() (*response.GetResponse, error) {
	return hc.get(hc.serverAddress + tagnames_ep)
}

// Returns a list of all tag values.
func (hc *httpClient) GetTagValues() (*response.GetResponse, error) {
	return hc.get(hc.serverAddress + tagvalues_ep)
}

// Queries KairosDB using the query built using builder.
func (hc *httpClient) Query(qb builder.QueryBuilder) (*response.QueryResponse, error) {
	// Get the JSON representation of the query.
	data, err := qb.Build()
	if err != nil {
		return nil, err
	}

	return hc.postQuery(hc.serverAddress+query_ep, data)
}

// Sends metrics from the builder to the KairosDB server.
func (hc *httpClient) PushMetrics(mb builder.MetricBuilder) (*response.Response, error) {
	data, err := mb.Build()
	if err != nil {
		return nil, err
	}

	return hc.postData(hc.serverAddress+datapoints_ep, data)
}

// Deletes a metric. This is the metric and all its datapoints.
func (hc *httpClient) DeleteMetric(name string) (*response.Response, error) {
	return hc.delete(hc.serverAddress + delmetric_ep + name)
}

// Deletes data in KairosDB using the query built by the builder.
func (hc *httpClient) Delete(qb builder.QueryBuilder) (*response.Response, error) {
	data, err := qb.Build()
	if err != nil {
		return nil, err
	}

	return hc.postData(hc.serverAddress+deldatapoints_ep, data)
}

// Checks the health of the KairosDB Server.
func (hc *httpClient) HealthCheck() (*response.Response, error) {
	resp, err := hc.sendRequest(hc.serverAddress+health_ep, "GET")
	if err != nil {
		return nil, err
	}

	r := &response.Response{}
	r.SetStatusCode(resp.StatusCode)
	return r, nil
}

func (hc *httpClient) sendRequest(url, method string) (*http.Response, error) {
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("accept", "application/json")
	cli := &http.Client{}

	return cli.Do(req)
}

func (hc *httpClient) httpRespToResponse(httpResp *http.Response) (*response.Response, error) {
	resp := &response.Response{}
	resp.SetStatusCode(httpResp.StatusCode)

	if httpResp.StatusCode != http.StatusNoContent {
		// If the request has failed, then read the response body.
		defer httpResp.Body.Close()
		contents, err := ioutil.ReadAll(httpResp.Body)
		if err != nil {
			return nil, err
		} else {
			// Unmarshal the contents into Response object.
			err = json.Unmarshal(contents, resp)
			if err != nil {
				return nil, err
			}
		}
	}

	return resp, nil
}

func (hc *httpClient) httpRespToQueryResponse(httpResp *http.Response) (*response.QueryResponse, error) {
	// Read the HTTP response body.
	defer httpResp.Body.Close()
	contents, err := ioutil.ReadAll(httpResp.Body)
	if err != nil {
		return nil, err
	}

	qr := response.NewQueryResponse(httpResp.StatusCode)

	// Unmarshal the contents into QueryResponse object.
	err = json.Unmarshal(contents, qr)
	if err != nil {
		return nil, err
	}

	return qr, nil
}

func (hc *httpClient) get(url string) (*response.GetResponse, error) {
	resp, err := hc.sendRequest(url, "GET")
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()
	contents, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	} else {
		gr := response.NewGetResponse(resp.StatusCode)

		err = json.Unmarshal(contents, gr)
		if err != nil {
			return nil, err
		}

		return gr, nil
	}
}

func (hc *httpClient) postData(url string, data []byte) (*response.Response, error) {
	resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	return hc.httpRespToResponse(resp)
}

func (hc *httpClient) postQuery(url string, data []byte) (*response.QueryResponse, error) {
	resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	return hc.httpRespToQueryResponse(resp)
}

func (hc *httpClient) delete(url string) (*response.Response, error) {
	resp, err := hc.sendRequest(url, "DELETE")
	if err != nil {
		return nil, err
	}

	return hc.httpRespToResponse(resp)
}
<commit_msg>Sort the API endpoints.<commit_after>// Copyright 2016 Ajit Yagaty
//
// 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 client

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"

	"github.com/ajityagaty/go-kairosdb/builder"
	"github.com/ajityagaty/go-kairosdb/response"
)

var (
	api_version      = "/api/v1"
	datapoints_ep    = api_version + "/datapoints"
	deldatapoints_ep = api_version + "/datapoints/delete"
	query_ep         = api_version + "/datapoints/query"
	health_ep        = api_version + "/health/check"
	delmetric_ep     = api_version + "/metric/"
	metricnames_ep   = api_version + "/metricnames"
	tagnames_ep      = api_version + "/tagnames"
	tagvalues_ep     = api_version + "/tagvalues"
	version_ep       = api_version + "/version"
)

// This is the type that implements the Client interface.
type httpClient struct {
	serverAddress string
}

func NewHttpClient(serverAddress string) Client {
	return &httpClient{
		serverAddress: serverAddress,
	}
}

// Returns a list of all metrics names.
func (hc *httpClient) GetMetricNames() (*response.GetResponse, error) {
	return hc.get(hc.serverAddress + metricnames_ep)
}

// Returns a list of all tag names.
func (hc *httpClient) GetTagNames() (*response.GetResponse, error) {
	return hc.get(hc.serverAddress + tagnames_ep)
}

// Returns a list of all tag values.
func (hc *httpClient) GetTagValues() (*response.GetResponse, error) {
	return hc.get(hc.serverAddress + tagvalues_ep)
}

// Queries KairosDB using the query built using builder.
func (hc *httpClient) Query(qb builder.QueryBuilder) (*response.QueryResponse, error) {
	// Get the JSON representation of the query.
	data, err := qb.Build()
	if err != nil {
		return nil, err
	}

	return hc.postQuery(hc.serverAddress+query_ep, data)
}

// Sends metrics from the builder to the KairosDB server.
func (hc *httpClient) PushMetrics(mb builder.MetricBuilder) (*response.Response, error) {
	data, err := mb.Build()
	if err != nil {
		return nil, err
	}

	return hc.postData(hc.serverAddress+datapoints_ep, data)
}

// Deletes a metric. This is the metric and all its datapoints.
func (hc *httpClient) DeleteMetric(name string) (*response.Response, error) {
	return hc.delete(hc.serverAddress + delmetric_ep + name)
}

// Deletes data in KairosDB using the query built by the builder.
func (hc *httpClient) Delete(qb builder.QueryBuilder) (*response.Response, error) {
	data, err := qb.Build()
	if err != nil {
		return nil, err
	}

	return hc.postData(hc.serverAddress+deldatapoints_ep, data)
}

// Checks the health of the KairosDB Server.
func (hc *httpClient) HealthCheck() (*response.Response, error) {
	resp, err := hc.sendRequest(hc.serverAddress+health_ep, "GET")
	if err != nil {
		return nil, err
	}

	r := &response.Response{}
	r.SetStatusCode(resp.StatusCode)
	return r, nil
}

func (hc *httpClient) sendRequest(url, method string) (*http.Response, error) {
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("accept", "application/json")
	cli := &http.Client{}

	return cli.Do(req)
}

func (hc *httpClient) httpRespToResponse(httpResp *http.Response) (*response.Response, error) {
	resp := &response.Response{}
	resp.SetStatusCode(httpResp.StatusCode)

	if httpResp.StatusCode != http.StatusNoContent {
		// If the request has failed, then read the response body.
		defer httpResp.Body.Close()
		contents, err := ioutil.ReadAll(httpResp.Body)
		if err != nil {
			return nil, err
		} else {
			// Unmarshal the contents into Response object.
			err = json.Unmarshal(contents, resp)
			if err != nil {
				return nil, err
			}
		}
	}

	return resp, nil
}

func (hc *httpClient) httpRespToQueryResponse(httpResp *http.Response) (*response.QueryResponse, error) {
	// Read the HTTP response body.
	defer httpResp.Body.Close()
	contents, err := ioutil.ReadAll(httpResp.Body)
	if err != nil {
		return nil, err
	}

	qr := response.NewQueryResponse(httpResp.StatusCode)

	// Unmarshal the contents into QueryResponse object.
	err = json.Unmarshal(contents, qr)
	if err != nil {
		return nil, err
	}

	return qr, nil
}

func (hc *httpClient) get(url string) (*response.GetResponse, error) {
	resp, err := hc.sendRequest(url, "GET")
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()
	contents, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	} else {
		gr := response.NewGetResponse(resp.StatusCode)

		err = json.Unmarshal(contents, gr)
		if err != nil {
			return nil, err
		}

		return gr, nil
	}
}

func (hc *httpClient) postData(url string, data []byte) (*response.Response, error) {
	resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	return hc.httpRespToResponse(resp)
}

func (hc *httpClient) postQuery(url string, data []byte) (*response.QueryResponse, error) {
	resp, err := http.Post(url, "application/json", bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	return hc.httpRespToQueryResponse(resp)
}

func (hc *httpClient) delete(url string) (*response.Response, error) {
	resp, err := hc.sendRequest(url, "DELETE")
	if err != nil {
		return nil, err
	}

	return hc.httpRespToResponse(resp)
}
<|endoftext|>


<commit_before>package networkwebsockets

import (
	"log"
	"testing"
)

func createClient(t *testing.T, urlStr string) *NetworkWebSocketClient {
	client, _, err := Dial(urlStr)
	if err != nil {
		t.Fatalf("Dial: ", err)
	}
	return client
}

func getClientId(client *NetworkWebSocketClient) string {
	// Request client's peer id
	client.SendStatusRequest()
	// Wait for response
	message := <-client.Status
	// Return client's peer id
	return message.Target
}

func checkConnect(t *testing.T, message NetworkWebSocketWireMessage, expectedTarget string) {
	if message.Target != expectedTarget {
		t.Fatalf("connect=%s, want %s", message.Target, expectedTarget)
	}
}

func checkBroadcast(t *testing.T, payload string, sender *NetworkWebSocketClient, receivers []*NetworkWebSocketClient) {
	// send broadcast message from sender
	sender.SendBroadcastData(payload)

	// check broadcast message arrived at all receivers
	for _, receiver := range receivers {
		message := <-receiver.Broadcast
		if message.Payload != payload {
			t.Fatalf("broadcast=%s, want %s", message.Payload, payload)
		}
	}
}

func checkMessage(t *testing.T, payload string, targetId string, sender *NetworkWebSocketClient, receiver *NetworkWebSocketClient) {
	if targetId == "" {
		t.Fatalf("No target identifier provided")
	}

	// send broadcast message from sender
	sender.SendMessageData(payload, targetId)

	// check broadcast message arrived at all receivers
	message := <-receiver.Message
	if message.Payload != payload {
		t.Fatalf("message=%s, want %s", message.Payload, payload)
	}
}

// TEST CASES

func Test_SameProxyClients(t *testing.T) {

	service := NewNetworkWebSocketService("localhost", 20100)
	_ = service.Start()

	// Create new Network Web Socket channel peers
	client1 := createClient(t, "ws://localhost:20100/myexampleservice")
	client2 := createClient(t, "ws://localhost:20100/myexampleservice")
	client3 := createClient(t, "ws://localhost:20100/myexampleservice")

	// Test status messaging (+ store client ids for future tests)
	client1Id := getClientId(client1)
	client2Id := getClientId(client2)
	client3Id := getClientId(client3)

	// Test connect messaging
	checkConnect(t, <-client1.Connect, client2Id)
	checkConnect(t, <-client1.Connect, client3Id)
	checkConnect(t, <-client2.Connect, client1Id)
	checkConnect(t, <-client2.Connect, client3Id)
	checkConnect(t, <-client3.Connect, client1Id)
	checkConnect(t, <-client3.Connect, client2Id)

	// Test broadcast messaging
	checkBroadcast(t, "hello world 1", client1, []*NetworkWebSocketClient{client2, client3})
	checkBroadcast(t, "hello world 2", client2, []*NetworkWebSocketClient{client1, client3})
	checkBroadcast(t, "hello world 3", client3, []*NetworkWebSocketClient{client1, client2})

	// Test direct messaging
	checkMessage(t, "direct message 1", client2Id, client1, client2)
	checkMessage(t, "direct message 2", client3Id, client1, client3)
	checkMessage(t, "direct message 3", client1Id, client2, client1)
	checkMessage(t, "direct message 4", client3Id, client2, client3)
	checkMessage(t, "direct message 5", client1Id, client3, client1)
	checkMessage(t, "direct message 6", client2Id, client3, client2)
}

func Test_MultipleProxyClients(t *testing.T) {

	service1 := NewNetworkWebSocketService("localhost", 20200)
	_ = service1.Start()

	service2 := NewNetworkWebSocketService("localhost", 20300)
	_ = service2.Start()

	// Create new Network Web Socket channel peers
	client1 := createClient(t, "ws://localhost:20200/testService2")
	client2 := createClient(t, "ws://localhost:20300/testService2")
	client3 := createClient(t, "ws://localhost:20300/testService2")

	// Test status messaging (+ store client ids for future tests)
	client1Id := getClientId(client1)
	client2Id := getClientId(client2)
	client3Id := getClientId(client3)

	log.Println("Waiting for Network Web Socket proxies to discover and connect to each other...")

	// Test connect messaging
	checkConnect(t, <-client1.Connect, client2Id)
	checkConnect(t, <-client1.Connect, client3Id)
	checkConnect(t, <-client2.Connect, client3Id)
	checkConnect(t, <-client2.Connect, client1Id)
	checkConnect(t, <-client3.Connect, client2Id)
	checkConnect(t, <-client3.Connect, client1Id)

	// Test broadcast messaging
	checkBroadcast(t, "hello world 1", client1, []*NetworkWebSocketClient{client2, client3})
	checkBroadcast(t, "hello world 2", client2, []*NetworkWebSocketClient{client1, client3})
	checkBroadcast(t, "hello world 3", client3, []*NetworkWebSocketClient{client1, client2})

	// Test direct messaging
	checkMessage(t, "direct message 1", client2Id, client1, client2)
	checkMessage(t, "direct message 2", client3Id, client1, client3)
	checkMessage(t, "direct message 3", client1Id, client2, client1)
	checkMessage(t, "direct message 4", client3Id, client2, client3)
	checkMessage(t, "direct message 5", client1Id, client3, client1)
	checkMessage(t, "direct message 6", client2Id, client3, client2)
}
<commit_msg>Test client disconnect functionality and channel disconnect messaging<commit_after>package networkwebsockets

import (
	"log"
	"testing"
)

func createClient(t *testing.T, urlStr string) *NetworkWebSocketClient {
	client, _, err := Dial(urlStr)
	if err != nil {
		t.Fatalf("Dial: ", err)
	}
	return client
}

func getClientId(client *NetworkWebSocketClient) string {
	// Request client's peer id
	client.SendStatusRequest()
	// Wait for response
	message := <-client.Status
	// Return client's peer id
	return message.Target
}

func checkConnect(t *testing.T, message NetworkWebSocketWireMessage, expectedTarget string) {
	if message.Target != expectedTarget {
		t.Fatalf("connect=%s, want %s", message.Target, expectedTarget)
	}
}

func checkDisconnect(t *testing.T, message NetworkWebSocketWireMessage, expectedTarget string) {
	if message.Target != expectedTarget {
		t.Fatalf("disconnect=%s, want %s", message.Target, expectedTarget)
	}
}

func checkBroadcast(t *testing.T, payload string, sender *NetworkWebSocketClient, receivers []*NetworkWebSocketClient) {
	// send broadcast message from sender
	sender.SendBroadcastData(payload)

	// check broadcast message arrived at all receivers
	for _, receiver := range receivers {
		message := <-receiver.Broadcast
		if message.Payload != payload {
			t.Fatalf("broadcast=%s, want %s", message.Payload, payload)
		}
	}
}

func checkMessage(t *testing.T, payload string, targetId string, sender *NetworkWebSocketClient, receiver *NetworkWebSocketClient) {
	if targetId == "" {
		t.Fatalf("No target identifier provided")
	}

	// send broadcast message from sender
	sender.SendMessageData(payload, targetId)

	// check broadcast message arrived at all receivers
	message := <-receiver.Message
	if message.Payload != payload {
		t.Fatalf("message=%s, want %s", message.Payload, payload)
	}
}

// TEST CASES

func Test_SameProxyClients(t *testing.T) {

	service := NewNetworkWebSocketService("localhost", 20100)
	_ = service.Start()

	// Create new Network Web Socket channel peers
	client1 := createClient(t, "ws://localhost:20100/myexampleservice")
	client2 := createClient(t, "ws://localhost:20100/myexampleservice")
	client3 := createClient(t, "ws://localhost:20100/myexampleservice")

	// Test status messaging (+ store client ids for future tests)
	client1Id := getClientId(client1)
	client2Id := getClientId(client2)
	client3Id := getClientId(client3)

	// Test connect messaging
	checkConnect(t, <-client1.Connect, client2Id)
	checkConnect(t, <-client1.Connect, client3Id)
	checkConnect(t, <-client2.Connect, client1Id)
	checkConnect(t, <-client2.Connect, client3Id)
	checkConnect(t, <-client3.Connect, client1Id)
	checkConnect(t, <-client3.Connect, client2Id)

	// Test broadcast messaging
	checkBroadcast(t, "hello world 1", client1, []*NetworkWebSocketClient{client2, client3})
	checkBroadcast(t, "hello world 2", client2, []*NetworkWebSocketClient{client1, client3})
	checkBroadcast(t, "hello world 3", client3, []*NetworkWebSocketClient{client1, client2})

	// Test direct messaging
	checkMessage(t, "direct message 1", client2Id, client1, client2)
	checkMessage(t, "direct message 2", client3Id, client1, client3)
	checkMessage(t, "direct message 3", client1Id, client2, client1)
	checkMessage(t, "direct message 4", client3Id, client2, client3)
	checkMessage(t, "direct message 5", client1Id, client3, client1)
	checkMessage(t, "direct message 6", client2Id, client3, client2)

	// Test disconnect messaging
	client1.Close()
	checkDisconnect(t, <-client2.Disconnect, client1Id)
	checkDisconnect(t, <-client3.Disconnect, client1Id)

	client2.Close()
	checkDisconnect(t, <-client3.Disconnect, client2Id)

	client3.Close()
}

func Test_MultipleProxyClients(t *testing.T) {

	service1 := NewNetworkWebSocketService("localhost", 20200)
	_ = service1.Start()

	service2 := NewNetworkWebSocketService("localhost", 20300)
	_ = service2.Start()

	// Create new Network Web Socket channel peers
	client1 := createClient(t, "ws://localhost:20200/testService2")
	client2 := createClient(t, "ws://localhost:20300/testService2")
	client3 := createClient(t, "ws://localhost:20300/testService2")

	// Test status messaging (+ store client ids for future tests)
	client1Id := getClientId(client1)
	client2Id := getClientId(client2)
	client3Id := getClientId(client3)

	log.Println("Waiting for Network Web Socket proxies to discover and connect to each other...")

	// Test connect messaging
	checkConnect(t, <-client1.Connect, client2Id)
	checkConnect(t, <-client1.Connect, client3Id)
	checkConnect(t, <-client2.Connect, client3Id)
	checkConnect(t, <-client2.Connect, client1Id)
	checkConnect(t, <-client3.Connect, client2Id)
	checkConnect(t, <-client3.Connect, client1Id)

	// Test broadcast messaging
	checkBroadcast(t, "hello world 1", client1, []*NetworkWebSocketClient{client2, client3})
	checkBroadcast(t, "hello world 2", client2, []*NetworkWebSocketClient{client1, client3})
	checkBroadcast(t, "hello world 3", client3, []*NetworkWebSocketClient{client1, client2})

	// Test direct messaging
	checkMessage(t, "direct message 1", client2Id, client1, client2)
	checkMessage(t, "direct message 2", client3Id, client1, client3)
	checkMessage(t, "direct message 3", client1Id, client2, client1)
	checkMessage(t, "direct message 4", client3Id, client2, client3)
	checkMessage(t, "direct message 5", client1Id, client3, client1)
	checkMessage(t, "direct message 6", client2Id, client3, client2)

	// Test disconnect messaging
	client1.Close()
	checkDisconnect(t, <-client2.Disconnect, client1Id)
	checkDisconnect(t, <-client3.Disconnect, client1Id)

	client2.Close()
	checkDisconnect(t, <-client3.Disconnect, client2Id)

	client3.Close()
}
<|endoftext|>