<commit_before>// Copyright 2016 Olivier Mengué. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license that
// can be found in the LICENSE file.

package jsonptr_test

import (
	"encoding/json"
	"reflect"
	"testing"

	"github.com/dolmen-go/jsonptr"
)

var _ error = (*jsonptr.PtrError)(nil)

type getTester struct {
	t   *testing.T
	Get func(interface{}, string) (interface{}, error)
}

func (tester *getTester) checkGet(jsonData string, ptr string, expected interface{}) {
	t := tester.t
	t.Logf("%v => \"%v\"", jsonData, ptr)
	var data interface{}
	if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
		t.Logf("Can't unmarshal %v: %s\n", jsonData, err)
		t.Fail()
		return
	}

	// Get from a deserialized structure
	got, err := tester.Get(data, ptr)
	if err != nil {
		t.Fatalf("  unexpected error: %s\n", err)
		return
	}
	if !reflect.DeepEqual(got, expected) {
		t.Fatalf("Result error!\n  expected: %T %v\n       got: %T %v\n", expected, expected, got, got)
		return
	}

	// Get from the raw JSON document
	got, err = tester.Get(json.RawMessage(jsonData), ptr)
	if err != nil {
		t.Fatalf("  unexpected error: %s\n", err)
		return
	}
	if !reflect.DeepEqual(got, expected) {
		t.Fatalf("Result error!\n  expected: %T %v\n       got: %T %v\n", expected, expected, got, got)
	}
}

func (tester *getTester) runTest() {
	t := tester.t
	for _, doc := range []interface{}{
		"x",
		1,
		1.0,
		true,
		false,
		nil,
		[]interface{}{},
		map[string]interface{}{},
	} {
		got, err := tester.Get(doc, "")
		if err != nil {
			t.Logf("%T: unexpected error: %s\n", doc, err)
			t.Fail()
			continue
		}
		if !reflect.DeepEqual(got, doc) {
			t.Logf("%T: different response\n", doc)
			t.Fail()
		}
	}

	tester.checkGet(`"x"`, ``, "x")
	tester.checkGet(`["x"]`, ``, []interface{}{"x"})
	tester.checkGet(`["a","b"]`, `/0`, "a")
	tester.checkGet(`["a","b"]`, `/1`, "b")
	tester.checkGet(`{"a":"x"}`, `/a`, "x")
	tester.checkGet(`{"":"x"}`, `/`, "x")
	tester.checkGet(`{"~":"x"}`, `/~0`, "x")
	tester.checkGet(`{"/":"y"}`, `/~1`, "y")
	tester.checkGet(`{"~/":"z"}`, `/~0~1`, "z")
	tester.checkGet(`{"/~":"z"}`, `/~1~0`, "z")
	tester.checkGet(`{"~~~":"x"}`, `/~0~0~0`, "x")
	tester.checkGet(`{"~x~":"x"}`, `/~0x~0`, "x")
	tester.checkGet(`{"/~~/":"z"}`, `/~1~0~0~1`, "z")
	tester.checkGet(`{"1éé":"z"}`, `/1éé`, "z")
	tester.checkGet(`{"a":{}}`, `/a`, map[string]interface{}{})
	tester.checkGet(`{"a":[]}`, `/a`, []interface{}{})
	tester.checkGet(`{"a":[1,2]}`, `/a/0`, float64(1))
	tester.checkGet(`{"a":[1,2]}`, `/a/1`, float64(2))
	tester.checkGet(`{"a":[0,1,2,3,4,5,6,7,8,9,"x"]}`, `/a/10`, "x")
}

func TestGet(t *testing.T) {
	(&getTester{
		t:   t,
		Get: jsonptr.Get,
	}).runTest()
}

func checkSet(t *testing.T, jsonIn string, ptr string, value interface{}, jsonOut string) {
	t.Logf("%v + \"%v\" \"%v\"", jsonIn, ptr, value)
	var data interface{}
	if err := json.Unmarshal([]byte(jsonIn), &data); err != nil {
		t.Logf("Can't unmarshal %v: %s\n", jsonIn, err)
		t.Fail()
		return
	}

	err := jsonptr.Set(&data, ptr, value)
	if err != nil {
		t.Logf("  unexpected error: %s\n", err)
		t.Fail()
		return
	}
	out, err := json.Marshal(data)
	if err != nil {
		t.Logf("  can't marshal output: %s\n", err)
		t.Fail()
		return
	}
	// Try exact matching
	if string(out) == jsonOut {
		return
	}
	// Else unmarshal and compare with DeepEqual
	var expectedData interface{}
	if err := json.Unmarshal([]byte(jsonOut), &expectedData); err != nil {
		t.Logf("Can't unmarshal %v: %s\n", expectedData, err)
		t.Fail()
		return
	}

	if !reflect.DeepEqual(data, expectedData) {
		t.Logf("Result error!\n  expected: %s\n       got: %s\n",
			jsonOut, string(out))
		t.Fail()
	}
}

func TestSet(t *testing.T) {
	checkSet(t, `null`, ``, "x", `"x"`)
	checkSet(t, `null`, ``, 1, `1`)
	checkSet(t, `null`, ``, []interface{}{}, `[]`)
	checkSet(t, `null`, ``, map[string]interface{}{}, `{}`)
	checkSet(t, `[]`, ``, nil, `null`)
	checkSet(t, `{}`, ``, nil, `null`)
	// TODO more tests

	checkSet(t, `[null]`, `/0`, nil, `[null]`)
	checkSet(t, `[null]`, `/0`, true, `[true]`)
	// Appending
	checkSet(t, `[]`, `/-`, true, `[true]`)
	checkSet(t, `[]`, `/0`, true, `[true]`)
	checkSet(t, `{}`, `/ok`, true, `{"ok":true}`)
	checkSet(t, `{"x":[]}`, `/x/-`, true, `{"x":[true]}`)
}
<commit_msg>More Get tests<commit_after>// Copyright 2016 Olivier Mengué. All rights reserved.
// Use of this source code is governed by the Apache 2.0 license that
// can be found in the LICENSE file.

package jsonptr_test

import (
	"encoding/json"
	"reflect"
	"testing"

	"github.com/dolmen-go/jsonptr"
)

var _ error = (*jsonptr.PtrError)(nil)

type getTester struct {
	t   *testing.T
	Get func(interface{}, string) (interface{}, error)
}

func (tester *getTester) checkGet(jsonData string, ptr string, expected interface{}) {
	t := tester.t
	t.Logf("%v => \"%v\"", jsonData, ptr)
	var data interface{}
	if err := json.Unmarshal([]byte(jsonData), &data); err != nil {
		t.Logf("Can't unmarshal %v: %s\n", jsonData, err)
		t.Fail()
		return
	}

	// Get from a deserialized structure
	got, err := tester.Get(data, ptr)
	if err != nil {
		t.Fatalf("  unexpected error: %s\n", err)
		return
	}
	if !reflect.DeepEqual(got, expected) {
		t.Fatalf("Result error!\n  expected: %T %v\n       got: %T %v\n", expected, expected, got, got)
		return
	}

	// Get from the raw JSON document
	got, err = tester.Get(json.RawMessage(jsonData), ptr)
	if err != nil {
		t.Fatalf("  unexpected error: %s\n", err)
		return
	}
	if !reflect.DeepEqual(got, expected) {
		t.Fatalf("Result error!\n  expected: %T %v\n       got: %T %v\n", expected, expected, got, got)
	}
}

func (tester *getTester) runTest() {
	t := tester.t
	for _, doc := range []interface{}{
		"x",
		1,
		1.0,
		true,
		false,
		nil,
		[]interface{}{},
		map[string]interface{}{},
	} {
		got, err := tester.Get(doc, "")
		if err != nil {
			t.Logf("%T: unexpected error: %s\n", doc, err)
			t.Fail()
			continue
		}
		if !reflect.DeepEqual(got, doc) {
			t.Logf("%T: different response\n", doc)
			t.Fail()
		}
	}

	tester.checkGet(`"x"`, ``, "x")
	tester.checkGet(`["x"]`, ``, []interface{}{"x"})
	tester.checkGet(`["a","b"]`, `/0`, "a")
	tester.checkGet(`["a","b"]`, `/1`, "b")
	tester.checkGet(`{"a":"x"}`, `/a`, "x")
	tester.checkGet(`{"":"x"}`, `/`, "x")
	tester.checkGet(`{"~":"x"}`, `/~0`, "x")
	tester.checkGet(`{"/":"y"}`, `/~1`, "y")
	tester.checkGet(`{"~/":"z"}`, `/~0~1`, "z")
	tester.checkGet(`{"/~":"z"}`, `/~1~0`, "z")
	tester.checkGet(`{"~~~":"x"}`, `/~0~0~0`, "x")
	tester.checkGet(`{"~x~":"x"}`, `/~0x~0`, "x")
	tester.checkGet(`{"/~~/":"z"}`, `/~1~0~0~1`, "z")
	tester.checkGet(`{"1éé":"z"}`, `/1éé`, "z")
	tester.checkGet(`{"a":{}}`, `/a`, map[string]interface{}{})
	tester.checkGet(`{"a":[]}`, `/a`, []interface{}{})
	tester.checkGet(`{"a":[1,2]}`, `/a/0`, float64(1))
	tester.checkGet(`{"a":[1,2]}`, `/a/1`, float64(2))
	tester.checkGet(`{"b":null,"a":[1,2]}`, `/a/1`, float64(2))
	tester.checkGet(`{"a":[0,1,2,3,4,5,6,7,8,9,"x"]}`, `/a/10`, "x")
}

func TestGet(t *testing.T) {
	(&getTester{
		t:   t,
		Get: jsonptr.Get,
	}).runTest()
}

func checkSet(t *testing.T, jsonIn string, ptr string, value interface{}, jsonOut string) {
	t.Logf("%v + \"%v\" \"%v\"", jsonIn, ptr, value)
	var data interface{}
	if err := json.Unmarshal([]byte(jsonIn), &data); err != nil {
		t.Logf("Can't unmarshal %v: %s\n", jsonIn, err)
		t.Fail()
		return
	}

	err := jsonptr.Set(&data, ptr, value)
	if err != nil {
		t.Logf("  unexpected error: %s\n", err)
		t.Fail()
		return
	}
	out, err := json.Marshal(data)
	if err != nil {
		t.Logf("  can't marshal output: %s\n", err)
		t.Fail()
		return
	}
	// Try exact matching
	if string(out) == jsonOut {
		return
	}
	// Else unmarshal and compare with DeepEqual
	var expectedData interface{}
	if err := json.Unmarshal([]byte(jsonOut), &expectedData); err != nil {
		t.Logf("Can't unmarshal %v: %s\n", expectedData, err)
		t.Fail()
		return
	}

	if !reflect.DeepEqual(data, expectedData) {
		t.Logf("Result error!\n  expected: %s\n       got: %s\n",
			jsonOut, string(out))
		t.Fail()
	}
}

func TestSet(t *testing.T) {
	checkSet(t, `null`, ``, "x", `"x"`)
	checkSet(t, `null`, ``, 1, `1`)
	checkSet(t, `null`, ``, []interface{}{}, `[]`)
	checkSet(t, `null`, ``, map[string]interface{}{}, `{}`)
	checkSet(t, `[]`, ``, nil, `null`)
	checkSet(t, `{}`, ``, nil, `null`)
	// TODO more tests

	checkSet(t, `[null]`, `/0`, nil, `[null]`)
	checkSet(t, `[null]`, `/0`, true, `[true]`)
	// Appending
	checkSet(t, `[]`, `/-`, true, `[true]`)
	checkSet(t, `[]`, `/0`, true, `[true]`)
	checkSet(t, `{}`, `/ok`, true, `{"ok":true}`)
	checkSet(t, `{"x":[]}`, `/x/-`, true, `{"x":[true]}`)
}
<|endoftext|>


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

// +build !windows

package gps

import (
	"bytes"
	"context"
	"os"
	"os/exec"
	"syscall"
	"time"

	"github.com/pkg/errors"
)

type cmd struct {
	// ctx is provided by the caller; SIGINT is sent when it is cancelled.
	ctx context.Context
	// cancel is called when the graceful shutdown timeout expires.
	cancel context.CancelFunc
	Cmd    *exec.Cmd
}

func commandContext(ctx context.Context, name string, arg ...string) cmd {
	// Create a one-off cancellable context for use by the CommandContext, in
	// the event that we have to force a Process.Kill().
	ctx2, cancel := context.WithCancel(context.Background())

	c := cmd{
		Cmd:    exec.CommandContext(ctx2, name, arg...),
		cancel: cancel,
		ctx:    ctx,
	}

	// Force subprocesses into their own process group, rather than being in the
	// same process group as the dep process. Because Ctrl-C sent from a
	// terminal will send the signal to the entire currently running process
	// group, this allows us to directly manage the issuance of signals to
	// subprocesses.
	c.Cmd.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
		Pgid:    0,
	}

	return c
}

// CombinedOutput is like (*os/exec.Cmd).CombinedOutput except that it
// terminates subprocesses gently (via os.Interrupt), but resorts to Kill if
// the subprocess fails to exit after 1 minute.
func (c cmd) CombinedOutput() ([]byte, error) {
	// Adapted from (*os/exec.Cmd).CombinedOutput
	if c.Cmd.Stdout != nil {
		return nil, errors.New("exec: Stdout already set")
	}
	if c.Cmd.Stderr != nil {
		return nil, errors.New("exec: Stderr already set")
	}
	var b bytes.Buffer
	c.Cmd.Stdout = &b
	c.Cmd.Stderr = &b

	if err := c.Cmd.Start(); err != nil {
		return nil, err
	}

	// Adapted from (*os/exec.Cmd).Start
	waitDone := make(chan struct{})
	defer close(waitDone)
	go func() {
		select {
		case <-c.ctx.Done():
			if err := c.Cmd.Process.Signal(os.Interrupt); err != nil {
				// If an error comes back from attempting to signal, proceed
				// immediately to hard kill.
				c.cancel()
			} else {
				stopCancel := time.AfterFunc(time.Minute, c.cancel).Stop
				<-waitDone
				stopCancel()
			}
		case <-waitDone:
		}
	}()

	if err := c.Cmd.Wait(); err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}
<commit_msg>gps: cmd_unix: remove extra command context; use Cmd.Process.Kill() directly<commit_after>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !windows

package gps

import (
	"bytes"
	"context"
	"os"
	"os/exec"
	"syscall"
	"time"

	"github.com/pkg/errors"
)

type cmd struct {
	// ctx is provided by the caller; SIGINT is sent when it is cancelled.
	ctx context.Context
	Cmd *exec.Cmd
}

func commandContext(ctx context.Context, name string, arg ...string) cmd {
	c := exec.Command(name, arg...)

	// Force subprocesses into their own process group, rather than being in the
	// same process group as the dep process. Because Ctrl-C sent from a
	// terminal will send the signal to the entire currently running process
	// group, this allows us to directly manage the issuance of signals to
	// subprocesses.
	c.SysProcAttr = &syscall.SysProcAttr{
		Setpgid: true,
		Pgid:    0,
	}

	return cmd{ctx: ctx, Cmd: c}
}

// CombinedOutput is like (*os/exec.Cmd).CombinedOutput except that it
// terminates subprocesses gently (via os.Interrupt), but resorts to Kill if
// the subprocess fails to exit after 1 minute.
func (c cmd) CombinedOutput() ([]byte, error) {
	// Adapted from (*os/exec.Cmd).CombinedOutput
	if c.Cmd.Stdout != nil {
		return nil, errors.New("exec: Stdout already set")
	}
	if c.Cmd.Stderr != nil {
		return nil, errors.New("exec: Stderr already set")
	}
	var b bytes.Buffer
	c.Cmd.Stdout = &b
	c.Cmd.Stderr = &b
	if err := c.Cmd.Start(); err != nil {
		return nil, err
	}

	// Adapted from (*os/exec.Cmd).Start
	waitDone := make(chan struct{})
	defer close(waitDone)
	go func() {
		select {
		case <-c.ctx.Done():
			if err := c.Cmd.Process.Signal(os.Interrupt); err != nil {
				// If an error comes back from attempting to signal, proceed
				// immediately to hard kill.
				_ = c.Cmd.Process.Kill()
			} else {
				defer time.AfterFunc(time.Minute, func() {
					_ = c.Cmd.Process.Kill()
				}).Stop()
				<-waitDone
			}
		case <-waitDone:
		}
	}()

	if err := c.Cmd.Wait(); err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 repr

import (
	"fmt"

	"github.com/golang/protobuf/proto"
	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/repr/proto"
)

func convertType(t fs.EntryType) (repr_proto.DirectoryEntryProto_Type, error) {
	switch t {
	case fs.TypeFile:
		return repr_proto.DirectoryEntryProto_TYPE_FILE, nil
	case fs.TypeDirectory:
		return repr_proto.DirectoryEntryProto_TYPE_DIRECTORY, nil
	case fs.TypeSymlink:
		return repr_proto.DirectoryEntryProto_TYPE_SYMLINK, nil
	case fs.TypeBlockDevice:
		return repr_proto.DirectoryEntryProto_TYPE_BLOCK_DEVICE, nil
	case fs.TypeCharDevice:
		return repr_proto.DirectoryEntryProto_TYPE_CHAR_DEVICE, nil
	case fs.TypeNamedPipe:
		return repr_proto.DirectoryEntryProto_TYPE_NAMED_PIPE, nil
	}

	return 0, fmt.Errorf("Unrecognized EntryType: %v", t)
}

func makeEntryProto(
	entry *fs.DirectoryEntry) (*repr_proto.DirectoryEntryProto, error) {
	blobs := []*repr_proto.BlobInfoProto{}
	for i, _ := range entry.Scores {
		// Make a copy of the score (a value type, not a reference type), for
		// slicing below.
		var score blob.Score = entry.Scores[i]

		proto := &repr_proto.BlobInfoProto{Hash: score[:]}
		blobs = append(blobs, proto)
	}

	entryProto := &repr_proto.DirectoryEntryProto{
		Permissions:    proto.Uint32(uint32(entry.Permissions)),
		Uid:            proto.Uint32(uint32(entry.Uid)),
		Username:       entry.Username,
		Gid:            proto.Uint32(uint32(entry.Gid)),
		Groupname:      entry.Groupname,
		HardLinkTarget: entry.HardLinkTarget,
		Name:           proto.String(entry.Name),
		Mtime: &repr_proto.TimeProto{
			Second:     proto.Int64(entry.MTime.Unix()),
			Nanosecond: proto.Uint32(uint32(entry.MTime.Nanosecond())),
		},
		Blob: blobs,
	}

	// Handle symlink targets.
	if entry.Type == fs.TypeSymlink {
		entryProto.Target = proto.String(entry.Target)
	}

	// Handle device numbers.
	if entry.Type == fs.TypeBlockDevice || entry.Type == fs.TypeCharDevice {
		entryProto.DeviceNumber = proto.Int32(entry.DeviceNumber)
	}

	// Convert the entry's type.
	typeEnum, err := convertType(entry.Type)
	if err != nil {
		return nil, err
	}

	entryProto.Type = typeEnum.Enum()

	return entryProto, nil
}

const (
	magicByte_Dir  byte = 'd'
	magicByte_File byte = 'f'
)

// MarshalDir turns a list of directory entries into bytes that can later be
// used with IsDir and UnmarshalDir. Note that ContainingDevice and Inode
// fields are lost.
//
// The input array may be modified.
func MarshalDir(entries []*fs.DirectoryEntry) (d []byte, err error) {
	// Set entry proto buffers.
	entryProtos := []*repr_proto.DirectoryEntryProto{}
	for _, entry := range entries {
		entryProto, err := makeEntryProto(entry)
		if err != nil {
			return nil, err
		}

		entryProtos = append(entryProtos, entryProto)
	}

	// Encapsulate the entries into a listing proto and serialize that.
	listingProto := &repr_proto.DirectoryListingProto{Entry: entryProtos}

	d, err = proto.Marshal(listingProto)
	if err != nil {
		err = fmt.Errorf("proto.Marshal: %v", err)
		return
	}

	// Append a magic byte so IsDir can recognize this as a directory.
	d = append(d, magicByte_Dir)

	return
}

// MarshalFile encodes the supplied file contents into bytes that can later be
// used with IsDir and UnmarshalFile. The input array may be modified.
func MarshalFile(contents []byte) (f []byte, err error) {
	// Append a magic byte so IsDir can recognize this as a file.
	f = append(contents, magicByte_File)
	return
}

// IsDir returns true if the supplied data should be decoded with UnmarshalDir,
// and false if it should be decoded with UnmarshalFile. In either case, the
// error code should be checked because this function does not check for valid
// data.
func IsDir(buf []byte) (dir bool) {
	l := len(buf)
	dir = l > 0 && buf[l-1] == magicByte_Dir
	return
}
<commit_msg>MarshalDir<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 repr

import (
	"fmt"

	"github.com/golang/protobuf/proto"
	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/repr/proto"
)

func convertType(t fs.EntryType) (repr_proto.DirectoryEntryProto_Type, error) {
	switch t {
	case fs.TypeFile:
		return repr_proto.DirectoryEntryProto_TYPE_FILE, nil
	case fs.TypeDirectory:
		return repr_proto.DirectoryEntryProto_TYPE_DIRECTORY, nil
	case fs.TypeSymlink:
		return repr_proto.DirectoryEntryProto_TYPE_SYMLINK, nil
	case fs.TypeBlockDevice:
		return repr_proto.DirectoryEntryProto_TYPE_BLOCK_DEVICE, nil
	case fs.TypeCharDevice:
		return repr_proto.DirectoryEntryProto_TYPE_CHAR_DEVICE, nil
	case fs.TypeNamedPipe:
		return repr_proto.DirectoryEntryProto_TYPE_NAMED_PIPE, nil
	}

	return 0, fmt.Errorf("Unrecognized EntryType: %v", t)
}

func makeEntryProto(
	entry *fs.DirectoryEntry) (*repr_proto.DirectoryEntryProto, error) {
	blobs := []*repr_proto.BlobInfoProto{}
	for i, _ := range entry.Scores {
		// Make a copy of the score (a value type, not a reference type), for
		// slicing below.
		var score blob.Score = entry.Scores[i]

		proto := &repr_proto.BlobInfoProto{Hash: score[:]}
		blobs = append(blobs, proto)
	}

	entryProto := &repr_proto.DirectoryEntryProto{
		Permissions:    proto.Uint32(uint32(entry.Permissions)),
		Uid:            proto.Uint32(uint32(entry.Uid)),
		Username:       entry.Username,
		Gid:            proto.Uint32(uint32(entry.Gid)),
		Groupname:      entry.Groupname,
		HardLinkTarget: entry.HardLinkTarget,
		Name:           proto.String(entry.Name),
		Mtime: &repr_proto.TimeProto{
			Second:     proto.Int64(entry.MTime.Unix()),
			Nanosecond: proto.Uint32(uint32(entry.MTime.Nanosecond())),
		},
		Inode: proto.Uint64(entry.Inode),
		Blob:  blobs,
	}

	// Handle symlink targets.
	if entry.Type == fs.TypeSymlink {
		entryProto.Target = proto.String(entry.Target)
	}

	// Handle device numbers.
	if entry.Type == fs.TypeBlockDevice || entry.Type == fs.TypeCharDevice {
		entryProto.DeviceNumber = proto.Int32(entry.DeviceNumber)
	}

	// Convert the entry's type.
	typeEnum, err := convertType(entry.Type)
	if err != nil {
		return nil, err
	}

	entryProto.Type = typeEnum.Enum()

	return entryProto, nil
}

const (
	magicByte_Dir  byte = 'd'
	magicByte_File byte = 'f'
)

// MarshalDir turns a list of directory entries into bytes that can later be
// used with IsDir and UnmarshalDir. Note that ContainingDevice fields are
// lost.
//
// The input array may be modified.
func MarshalDir(entries []*fs.DirectoryEntry) (d []byte, err error) {
	// Set entry proto buffers.
	entryProtos := []*repr_proto.DirectoryEntryProto{}
	for _, entry := range entries {
		entryProto, err := makeEntryProto(entry)
		if err != nil {
			return nil, err
		}

		entryProtos = append(entryProtos, entryProto)
	}

	// Encapsulate the entries into a listing proto and serialize that.
	listingProto := &repr_proto.DirectoryListingProto{Entry: entryProtos}

	d, err = proto.Marshal(listingProto)
	if err != nil {
		err = fmt.Errorf("proto.Marshal: %v", err)
		return
	}

	// Append a magic byte so IsDir can recognize this as a directory.
	d = append(d, magicByte_Dir)

	return
}

// MarshalFile encodes the supplied file contents into bytes that can later be
// used with IsDir and UnmarshalFile. The input array may be modified.
func MarshalFile(contents []byte) (f []byte, err error) {
	// Append a magic byte so IsDir can recognize this as a file.
	f = append(contents, magicByte_File)
	return
}

// IsDir returns true if the supplied data should be decoded with UnmarshalDir,
// and false if it should be decoded with UnmarshalFile. In either case, the
// error code should be checked because this function does not check for valid
// data.
func IsDir(buf []byte) (dir bool) {
	l := len(buf)
	dir = l > 0 && buf[l-1] == magicByte_Dir
	return
}
<|endoftext|>


<commit_before>package runner

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/cucumber/godog"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
	"github.com/ii/xds-test-harness/api/adapter"
	pb "github.com/ii/xds-test-harness/api/adapter"
	parser "github.com/ii/xds-test-harness/internal/parser"
	"github.com/rs/zerolog/log"
)

func itemInSlice(item string, slice []string) bool {
	for _, sliceItem := range slice {
		if item == sliceItem {
			return true
		}
	}
	return false
}

func versionsMatch(expected string, actual string) bool {
	return expected == actual
}

func resourcesMatch(expected []string, actual []string) bool {
	// Compare the resources in a discovery response to the ones we expect.
	// It is valid for the response to give more resources than subscribed to,
	// which is why we are not checking the equality of the two slices, only that
	// all of expected is contained in actual.
	for _, ec := range expected {
		if match := itemInSlice(ec, actual); match == false {
			return false
		}
	}
	return true
}

func (r *Runner) ATargetSetupWithServiceResourcesAndVersion(service, resources, version string) error {
	snapshot := &pb.Snapshot{
		Node:    r.NodeID,
		Version: fmt.Sprint(version),
	}
	resourceNames := strings.Split(resources, ",")

	//Set endpoints
	endpoints := parser.ToEndpoints(resourceNames)
	snapshot.Endpoints = endpoints

	//Set clusters
	clusters := parser.ToClusters(resourceNames)
	snapshot.Clusters = clusters

	//Set Routes
	routes := parser.ToRoutes(resourceNames)
	snapshot.Routes = routes

	//Set listeners
	listeners := parser.ToListeners(resourceNames)
	snapshot.Listeners = listeners

	//Set runtimes
	runtimes := parser.ToRuntimes(resourceNames)
	snapshot.Runtimes = runtimes

	c := pb.NewAdapterClient(r.Adapter.Conn)

	_, err := c.SetState(context.Background(), snapshot)
	if err != nil {
		msg := "Cannot set target with given state"
		log.Error().
			Err(err).
			Msg(msg)
		return errors.New(msg)
	}

	r.Cache.StartState = snapshot
	return nil
}

func (r *Runner) TheClientDoesAWildcardSubscriptionToService(service string) error {
	resources := []string{}
	r.ClientSubscribesToServiceForResources(service, resources)
	return nil
}

func (r *Runner) ClientSubscribesToASubsetOfResourcesForService(subset, service string) error {
	resources := strings.Split(subset, ",")
	r.ClientSubscribesToServiceForResources(service, resources)
	return nil
}

func (r *Runner) ClientSubscribesToServiceForResources(srv string, resources []string) error {
	builder := getBuilder(srv)
	builder.openChannels()
	builder.setInitResources(resources)
	err := builder.setStream(r.Target.Conn)
	if err != nil {
		return err
	}
	r.Service = builder.getService()

	request := r.NewRequest(r.Service.Cache.InitResource, r.Service.TypeURL)

	log.Debug().
		Msgf("Sending subscribing request: %v\n", request)
	go r.Stream(r.Service)
	go r.Ack(request, r.Service)
	return nil
}

func (r *Runner) TheClientReceivesCorrectResourcesAndVersionForService(resources, version, service string) error {
	expectedResources := strings.Split(resources, ",")
	stream := r.Service

	for {
		select {
		case err := <-stream.Channels.Err:
			log.Err(err).Msg("From our step")
			return errors.New("Could not find expected response within grace period of 10 seconds.")
		default:
			if len(stream.Cache.Responses) > 0 {
				for _, response := range stream.Cache.Responses {
					log.Debug().Msgf("Response: %v", response)
					actual, err := parser.ParseDiscoveryResponseV2(response)
					if err != nil {
						log.Error().Err(err).Msg("can't parse discovery response ")
						return err
					}
					if versionsMatch(version, actual.Version) && resourcesMatch(expectedResources, actual.Resources) {
						return nil
					}
				}
			}
		}
	}
}

func (r *Runner) TheClientReceivesCorrectResourcesAndVersion(resources, version string) error {
	expectedResources := strings.Split(resources, ",")
	stream := r.Service
	actualResources := []string{}

	for {
		select {
		case err := <-stream.Channels.Err:
			log.Err(err).Msg("From our step")
			return errors.New("Could not find expected response within grace period of 10 seconds.")
		default:
			if len(stream.Cache.Responses) > 0 {
				for _, response := range stream.Cache.Responses {
					log.Debug().Msgf("Response: %v", response)
					actual, err := parser.ParseDiscoveryResponseV2(response)
					if err != nil {
						log.Error().Err(err).Msg("can't parse discovery response ")
						return err
					}
					if versionsMatch(version, actual.Version) {
						if stream.Name == "RDS" {
							actualResources = append(actualResources, actual.Resources...)
						} else {
							actualResources = actual.Resources
						}
						if resourcesMatch(expectedResources, actualResources) {
							return nil
						}
					}
				}
			}
		}
	}
}

func (r *Runner) TheClientSendsAnACKToWhichTheDoesNotRespond(service string) error {
	stream := r.Service
	stream.Channels.Done <- true

	// give some time for the final messages to come through, if there's any lingering responses.
	time.Sleep(3 * time.Second)
	log.Debug().
		Msgf("Request Count: %v Response Count: %v", len(stream.Cache.Requests), len(stream.Cache.Responses))
	if len(stream.Cache.Requests) <= len(stream.Cache.Responses) {
		err := errors.New("There are more responses than requests.  This indicates the server responded to the last ack")
		log.Err(err).
			Msgf("Requests:%v, Responses: \v", stream.Cache.Requests, stream.Cache.Responses)
		return err
	}
	return nil
}

func (r *Runner) ResourceOfTheServiceIsUpdatedToNextVersion(resource, service, version string) error {
	snapshot := &adapter.Snapshot{
		Version: version,
	}
	startState := r.Cache.StartState
	log.Debug().
		Msgf("Updating target state for %v resource %v", service, resource)
	log.Debug().Msgf("Our cache start state version: %v", r.Cache.StartState.Version)
	snapshot.Node = startState.Node
	snapshot.Endpoints = startState.Endpoints
	snapshot.Clusters = startState.Clusters
	snapshot.Routes = startState.Routes
	snapshot.Listeners = startState.Listeners
	snapshot.Runtimes = startState.Runtimes
	snapshot.Secrets = startState.Secrets
	log.Debug().Msgf("Our cache start state version after copy: %v", r.Cache.StartState.Version)
	log.Debug().Msgf("Our snapshot version: %v", snapshot.Version)

	if service == "LDS" {
		listeners := snapshot.GetListeners()
		for _, listener := range listeners.Items {
			if listener.Name == resource {
				listener.Address = parser.RandomAddress()
			}
		}
		snapshot.Listeners = listeners
	}

	if service == "CDS" {
		clusters := snapshot.GetClusters()
		for _, cluster := range clusters.Items {
			if cluster.Name == resource {
				cluster.ConnectTimeout = map[string]int32{"seconds": 10}
			}
		}
		snapshot.Clusters = clusters
	}

	c := pb.NewAdapterClient(r.Adapter.Conn)

	_, err := c.UpdateState(context.Background(), snapshot)
	if err != nil {
		msg := "Cannot update target with given state"
		log.Error().
			Err(err).
			Msg(msg)
		return errors.New(msg)
	}

	r.Cache.StateSnapshots = append(r.Cache.StateSnapshots, snapshot)

	return nil
}

func (r *Runner) ResourceIsAddedToServiceWithVersion(resource, service, version string) error {
	log.Debug().
		Msgf("Adding %v to %v service", resource, service)

	snapshot := r.Cache.StartState
	snapshot.Version = version

	//Set endpoints
	endpoints := snapshot.GetEndpoints()
	endpoints.Items = append(endpoints.Items, &pb.Endpoint{
		Name:    resource,
		Cluster: resource,
		Address: parser.RandomAddress(),
	})
	snapshot.Endpoints = endpoints

	//Set clusters
	clusters := snapshot.GetClusters()
	clusters.Items = append(clusters.Items, &pb.Cluster{
		Name:           resource,
		ConnectTimeout: map[string]int32{"seconds": 5},
	})
	snapshot.Clusters = clusters

	//Set Routes
	routes := snapshot.GetRoutes()
	routes.Items = append(routes.Items, &pb.Route{
		Name: resource,
	})
	snapshot.Routes = routes

	//Set listeners
	listeners := snapshot.GetListeners()
	listeners.Items = append(listeners.Items, &pb.Listener{
		Name:    resource,
		Address: parser.RandomAddress(),
	})

	//Set runtimes
	runtimes := snapshot.GetRuntimes()
	runtimes.Items = append(runtimes.Items, &pb.Runtime{
		Name: resource,
	})
	snapshot.Runtimes = runtimes

	c := pb.NewAdapterClient(r.Adapter.Conn)

	_, err := c.UpdateState(context.Background(), snapshot)
	if err != nil {
		msg := "Cannot update target with given state"
		log.Error().
			Err(err).
			Msg(msg)
		return errors.New(msg)
	}

	r.Cache.StateSnapshots = append(r.Cache.StateSnapshots, snapshot)

	return nil
}

func (r *Runner) ClientUpdatesSubscriptionToAResourceForServiceWithVersion(resource, service, version string) error {
	request := &discovery.DiscoveryRequest{
		VersionInfo:   version,
		ResourceNames: []string{resource},
		TypeUrl:       r.Service.TypeURL,
	}
	log.Debug().
		Msgf("Sending Request: %v", request)
	r.Service.Channels.Req <- request
	return nil
}

func (r *Runner) ClientUnsubscribesFromAllResourcesForService(service string) error {
	version := r.Cache.StartState.Version

	request := &discovery.DiscoveryRequest{
		VersionInfo:   version,
		ResourceNames: []string{""},
		TypeUrl:       r.Service.TypeURL,
	}
	time.Sleep(3 * time.Second)
	log.Debug().
		Msgf("Sending unsubscribe request: %v", request)
	r.Service.Channels.Req <- request
	time.Sleep(3 * time.Second)
	return nil
}

func (r *Runner) ClientDoesNotReceiveAnyMessageFromService(service string) error {
	for {
		select {
		case err := <-r.Service.Channels.Err:
			log.Err(err).Msg("From our step")
			return err
		default:
			if len(r.Service.Cache.Responses) > 0 {
				for _, response := range r.Service.Cache.Responses {
					actual, err := parser.ParseDiscoveryResponseV2(response)
					if err != nil {
						log.Error().
							Err(err).
							Msg("can't parse discovery response ")
						return err
					}
					err = errors.New("Received a response when we expected no response")
					log.Err(err).
						Msgf("Response: %v", actual)
					return err
				}
			}
		}
	}
}

func (r *Runner) LoadSteps(ctx *godog.ScenarioContext) {
	ctx.Step(`^a target setup with "([^"]*)", "([^"]*)", and "([^"]*)"$`, r.ATargetSetupWithServiceResourcesAndVersion)
	ctx.Step(`^the Client does a wildcard subscription to "([^"]*)"$`, r.TheClientDoesAWildcardSubscriptionToService)
	ctx.Step(`^the Client subscribes to a "([^"]*)" for "([^"]*)"$`, r.ClientSubscribesToASubsetOfResourcesForService)
	ctx.Step(`^the Client receives the "([^"]*)" and "([^"]*)"$`, r.TheClientReceivesCorrectResourcesAndVersion)
	ctx.Step(`^the Client does not receive any message from "([^"]*)"$`, r.ClientDoesNotReceiveAnyMessageFromService)
	ctx.Step(`^the Client sends an ACK to which the "([^"]*)" does not respond$`, r.TheClientSendsAnACKToWhichTheDoesNotRespond)
	ctx.Step(`^a "([^"]*)" of the "([^"]*)" is updated to the "([^"]*)"$`, r.ResourceOfTheServiceIsUpdatedToNextVersion)
	ctx.Step(`^a "([^"]*)" is added to the "([^"]*)" with "([^"]*)"$`, r.ResourceIsAddedToServiceWithVersion)
	ctx.Step(`^the Client updates subscription to a "([^"]*)" of "([^"]*)" with "([^"]*)"$`, r.ClientUpdatesSubscriptionToAResourceForServiceWithVersion)
	ctx.Step(`^the Client unsubscribes from all resources for "([^"]*)"$`, r.ClientUnsubscribesFromAllResourcesForService)
}
<commit_msg>update validation steps for RDS<commit_after>package runner

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/cucumber/godog"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
	"github.com/ii/xds-test-harness/api/adapter"
	pb "github.com/ii/xds-test-harness/api/adapter"
	parser "github.com/ii/xds-test-harness/internal/parser"
	"github.com/rs/zerolog/log"
)

func itemInSlice(item string, slice []string) bool {
	for _, sliceItem := range slice {
		if item == sliceItem {
			return true
		}
	}
	return false
}

func versionsMatch(expected string, actual string) bool {
	return expected == actual
}

func resourcesMatch(expected []string, actual []string) bool {
	log.Debug().Msgf("resources to match...expected: %v, actual: %v\n", expected, actual)
	// Compare the resources in a discovery response to the ones we expect.
	// It is valid for the response to give more resources than subscribed to,
	// which is why we are not checking the equality of the two slices, only that
	// all of expected is contained in actual.
	for _, ec := range expected {
		if match := itemInSlice(ec, actual); match == false {
			return false
		}
	}
	return true
}

func (r *Runner) ATargetSetupWithServiceResourcesAndVersion(service, resources, version string) error {
	snapshot := &pb.Snapshot{
		Node:    r.NodeID,
		Version: fmt.Sprint(version),
	}
	resourceNames := strings.Split(resources, ",")

	//Set endpoints
	endpoints := parser.ToEndpoints(resourceNames)
	snapshot.Endpoints = endpoints

	//Set clusters
	clusters := parser.ToClusters(resourceNames)
	snapshot.Clusters = clusters

	//Set Routes
	routes := parser.ToRoutes(resourceNames)
	snapshot.Routes = routes

	//Set listeners
	listeners := parser.ToListeners(resourceNames)
	snapshot.Listeners = listeners

	//Set runtimes
	runtimes := parser.ToRuntimes(resourceNames)
	snapshot.Runtimes = runtimes

	c := pb.NewAdapterClient(r.Adapter.Conn)

	_, err := c.SetState(context.Background(), snapshot)
	if err != nil {
		msg := "Cannot set target with given state"
		log.Error().
			Err(err).
			Msg(msg)
		return errors.New(msg)
	}

	r.Cache.StartState = snapshot
	return nil
}

func (r *Runner) TheClientDoesAWildcardSubscriptionToService(service string) error {
	resources := []string{}
	r.ClientSubscribesToServiceForResources(service, resources)
	return nil
}

func (r *Runner) ClientSubscribesToASubsetOfResourcesForService(subset, service string) error {
	resources := strings.Split(subset, ",")
	r.ClientSubscribesToServiceForResources(service, resources)
	return nil
}

func (r *Runner) ClientSubscribesToServiceForResources(srv string, resources []string) error {
	builder := getBuilder(srv)
	builder.openChannels()
	builder.setInitResources(resources)
	err := builder.setStream(r.Target.Conn)
	if err != nil {
		return err
	}
	r.Service = builder.getService()

	request := r.NewRequest(r.Service.Cache.InitResource, r.Service.TypeURL)

	log.Debug().
		Msgf("Sending subscribing request: %v\n", request)
	go r.Stream(r.Service)
	go r.Ack(request, r.Service)
	return nil
}

func (r *Runner) TheClientReceivesCorrectResourcesAndVersion(resources, version string) error {
	expectedResources := strings.Split(resources, ",")
	stream := r.Service
	actualResources := []string{}

	for {
		select {
		case err := <-stream.Channels.Err:
			log.Err(err).Msg("From our step")
			return errors.New("Could not find expected response within grace period of 10 seconds.")
		default:
			if len(stream.Cache.Responses) > 0 {
				for _, response := range stream.Cache.Responses {
					actual, err := parser.ParseDiscoveryResponseV2(response)
					if err != nil {
						log.Error().Err(err).Msg("can't parse discovery response ")
						return err
					}
					if !versionsMatch(version, actual.Version) {
						continue
					}
					if stream.Name == "RDS" {
						actualResources = append(actualResources, actual.Resources...)
					} else {
						actualResources = actual.Resources
					}
					if !resourcesMatch(expectedResources, actualResources) {
						continue
					}
					return nil
				}
			}
		}
	}
}

func (r *Runner) theClientReceivesOnlyTheCorrectResourceAndVersion(resource, version string) error {
	stream := r.Service
	log.Debug().Msgf("Resource: %v, version: %v", resource, version)

	for {
		select {
		case err := <-stream.Channels.Err:
			log.Err(err).Msg("From our step")
			return errors.New("Could not find expected response within grace period of 10 seconds.")
		default:
			if len(stream.Cache.Responses) > 0 {
				for _, response := range stream.Cache.Responses {
					actual, err := parser.ParseDiscoveryResponseV2(response)
					if err != nil {
						log.Error().Err(err).Msg("can't parse discovery response ")
						return err
					}
					if !versionsMatch(version, actual.Version) {
						continue
					}
					// we set our subscription to a single resource, and the services should only send a single resource.
					// If the resources slice is empty or more than one, it is incorrect and we can continue.
					// (this fn is not designed for LDS or CDS tests)
					if len(actual.Resources) != 1 {
						continue
					}
					if !resourcesMatch([]string{resource}, actual.Resources) {
						log.Debug().Msgf("resources don't match: %v", actual.Resources)
						continue
					}
					return nil
				}
			}
		}
	}
}

func (r *Runner) TheClientSendsAnACKToWhichTheDoesNotRespond(service string) error {
	stream := r.Service
	stream.Channels.Done <- true

	// give some time for the final messages to come through, if there's any lingering responses.
	time.Sleep(3 * time.Second)
	log.Debug().
		Msgf("Request Count: %v Response Count: %v", len(stream.Cache.Requests), len(stream.Cache.Responses))
	if len(stream.Cache.Requests) <= len(stream.Cache.Responses) {
		err := errors.New("There are more responses than requests.  This indicates the server responded to the last ack")
		log.Err(err).
			Msgf("Requests:%v, Responses: \v", stream.Cache.Requests, stream.Cache.Responses)
		return err
	}
	return nil
}

func (r *Runner) ResourceOfTheServiceIsUpdatedToNextVersion(resource, service, version string) error {
	snapshot := &adapter.Snapshot{
		Version: version,
	}
	startState := r.Cache.StartState
	log.Debug().
		Msgf("Updating target state for %v resource %v", service, resource)
	log.Debug().Msgf("Our cache start state version: %v", r.Cache.StartState.Version)
	snapshot.Node = startState.Node
	snapshot.Endpoints = startState.Endpoints
	snapshot.Clusters = startState.Clusters
	snapshot.Routes = startState.Routes
	snapshot.Listeners = startState.Listeners
	snapshot.Runtimes = startState.Runtimes
	snapshot.Secrets = startState.Secrets
	log.Debug().Msgf("Our cache start state version after copy: %v", r.Cache.StartState.Version)
	log.Debug().Msgf("Our snapshot version: %v", snapshot.Version)

	if service == "LDS" {
		listeners := snapshot.GetListeners()
		for _, listener := range listeners.Items {
			if listener.Name == resource {
				listener.Address = parser.RandomAddress()
			}
		}
		snapshot.Listeners = listeners
	}

	if service == "CDS" {
		clusters := snapshot.GetClusters()
		for _, cluster := range clusters.Items {
			if cluster.Name == resource {
				cluster.ConnectTimeout = map[string]int32{"seconds": 10}
			}
		}
		snapshot.Clusters = clusters
	}

	c := pb.NewAdapterClient(r.Adapter.Conn)

	_, err := c.UpdateState(context.Background(), snapshot)
	if err != nil {
		msg := "Cannot update target with given state"
		log.Error().
			Err(err).
			Msg(msg)
		return errors.New(msg)
	}

	r.Cache.StateSnapshots = append(r.Cache.StateSnapshots, snapshot)

	return nil
}

func (r *Runner) ResourceIsAddedToServiceWithVersion(resource, service, version string) error {
	log.Debug().
		Msgf("Adding %v to %v service", resource, service)

	snapshot := r.Cache.StartState
	snapshot.Version = version

	//Set endpoints
	endpoints := snapshot.GetEndpoints()
	endpoints.Items = append(endpoints.Items, &pb.Endpoint{
		Name:    resource,
		Cluster: resource,
		Address: parser.RandomAddress(),
	})
	snapshot.Endpoints = endpoints

	//Set clusters
	clusters := snapshot.GetClusters()
	clusters.Items = append(clusters.Items, &pb.Cluster{
		Name:           resource,
		ConnectTimeout: map[string]int32{"seconds": 5},
	})
	snapshot.Clusters = clusters

	//Set Routes
	routes := snapshot.GetRoutes()
	routes.Items = append(routes.Items, &pb.Route{
		Name: resource,
	})
	snapshot.Routes = routes

	//Set listeners
	listeners := snapshot.GetListeners()
	listeners.Items = append(listeners.Items, &pb.Listener{
		Name:    resource,
		Address: parser.RandomAddress(),
	})

	//Set runtimes
	runtimes := snapshot.GetRuntimes()
	runtimes.Items = append(runtimes.Items, &pb.Runtime{
		Name: resource,
	})
	snapshot.Runtimes = runtimes

	c := pb.NewAdapterClient(r.Adapter.Conn)

	_, err := c.UpdateState(context.Background(), snapshot)
	if err != nil {
		msg := "Cannot update target with given state"
		log.Error().
			Err(err).
			Msg(msg)
		return errors.New(msg)
	}

	r.Cache.StateSnapshots = append(r.Cache.StateSnapshots, snapshot)

	return nil
}

func (r *Runner) ClientUpdatesSubscriptionToAResourceForServiceWithVersion(resource, service, version string) error {
	request := &discovery.DiscoveryRequest{
		VersionInfo:   version,
		ResourceNames: []string{resource},
		TypeUrl:       r.Service.TypeURL,
	}
	// small hack as i build this out, to ensure Acking our last response happens before we update subscription
	time.Sleep(2 * time.Second)
	log.Debug().
		Msgf("Sending Request To Update Subscription: %v", request)
	r.Service.Channels.Req <- request
	return nil
}

func (r *Runner) ClientUnsubscribesFromAllResourcesForService(service string) error {
	version := r.Cache.StartState.Version

	request := &discovery.DiscoveryRequest{
		VersionInfo:   version,
		ResourceNames: []string{""},
		TypeUrl:       r.Service.TypeURL,
	}
	time.Sleep(3 * time.Second)
	log.Debug().
		Msgf("Sending unsubscribe request: %v", request)
	r.Service.Channels.Req <- request
	time.Sleep(3 * time.Second)
	return nil
}

func (r *Runner) ClientDoesNotReceiveAnyMessageFromService(service string) error {
	for {
		select {
		case err := <-r.Service.Channels.Err:
			log.Err(err).Msg("From our step")
			return err
		default:
			if len(r.Service.Cache.Responses) > 0 {
				for _, response := range r.Service.Cache.Responses {
					actual, err := parser.ParseDiscoveryResponseV2(response)
					if err != nil {
						log.Error().
							Err(err).
							Msg("can't parse discovery response ")
						return err
					}
					currentState := r.Cache.StateSnapshots[len(r.Cache.StateSnapshots) - 1]
					if actual.Version != currentState.Version {
						continue
					}
					err = errors.New("Received a response when we expected no response")
					log.Err(err).
						Msgf("Response: %v", actual)
					return err
				}
			}
		}
	}
}

func (r *Runner) LoadSteps(ctx *godog.ScenarioContext) {
	ctx.Step(`^a target setup with "([^"]*)", "([^"]*)", and "([^"]*)"$`, r.ATargetSetupWithServiceResourcesAndVersion)
	ctx.Step(`^the Client does a wildcard subscription to "([^"]*)"$`, r.TheClientDoesAWildcardSubscriptionToService)
	ctx.Step(`^the Client subscribes to a "([^"]*)" for "([^"]*)"$`, r.ClientSubscribesToASubsetOfResourcesForService)
	ctx.Step(`^the Client receives the "([^"]*)" and "([^"]*)"$`, r.TheClientReceivesCorrectResourcesAndVersion)
    ctx.Step(`^the Client receives only the "([^"]*)" and "([^"]*)"$`, r.theClientReceivesOnlyTheCorrectResourceAndVersion)
	ctx.Step(`^the Client does not receive any message from "([^"]*)"$`, r.ClientDoesNotReceiveAnyMessageFromService)
	ctx.Step(`^the Client sends an ACK to which the "([^"]*)" does not respond$`, r.TheClientSendsAnACKToWhichTheDoesNotRespond)
	ctx.Step(`^a "([^"]*)" of the "([^"]*)" is updated to the "([^"]*)"$`, r.ResourceOfTheServiceIsUpdatedToNextVersion)
	ctx.Step(`^a "([^"]*)" is added to the "([^"]*)" with "([^"]*)"$`, r.ResourceIsAddedToServiceWithVersion)
	ctx.Step(`^the Client updates subscription to a "([^"]*)" of "([^"]*)" with "([^"]*)"$`, r.ClientUpdatesSubscriptionToAResourceForServiceWithVersion)
	ctx.Step(`^the Client unsubscribes from all resources for "([^"]*)"$`, r.ClientUnsubscribesFromAllResourcesForService)
}
<|endoftext|>


<commit_before>package server

import (
	"math"
	"strconv"
	"time"

	"github.com/tidwall/geojson"
	"github.com/tidwall/geojson/geometry"
	"github.com/tidwall/gjson"
	"github.com/tidwall/tile38/internal/glob"
)

// FenceMatch executes a fence match returns back json messages for fence detection.
func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas []FenceMeta, details *commandDetails) []string {
	msgs := fenceMatch(hookName, sw, fence, metas, details)
	if len(fence.accept) == 0 {
		return msgs
	}
	nmsgs := make([]string, 0, len(msgs))
	for _, msg := range msgs {
		if fence.accept[gjson.Get(msg, "command").String()] {
			nmsgs = append(nmsgs, msg)
		}
	}
	return nmsgs
}
func appendHookDetails(b []byte, hookName string, metas []FenceMeta) []byte {
	if len(hookName) > 0 {
		b = append(b, `,"hook":`...)
		b = appendJSONString(b, hookName)
	}
	if len(metas) > 0 {
		b = append(b, `,"meta":{`...)
		for i, meta := range metas {
			if i > 0 {
				b = append(b, ',')
			}
			b = appendJSONString(b, meta.Name)
			b = append(b, ':')
			b = appendJSONString(b, meta.Value)
		}
		b = append(b, '}')
	}
	return b
}

func objIsSpatial(obj geojson.Object) bool {
	_, ok := obj.(geojson.Spatial)
	return ok
}

func hookJSONString(hookName string, metas []FenceMeta) string {
	return string(appendHookDetails(nil, hookName, metas))
}
func fenceMatch(
	hookName string, sw *scanWriter, fence *liveFenceSwitches,
	metas []FenceMeta, details *commandDetails,
) []string {
	if details.command == "drop" {
		return []string{
			`{"command":"drop"` + hookJSONString(hookName, metas) +
				`,"time":` + jsonTimeFormat(details.timestamp) + `}`,
		}
	}
	if len(fence.glob) > 0 && !(len(fence.glob) == 1 && fence.glob[0] == '*') {
		match, _ := glob.Match(fence.glob, details.id)
		if !match {
			return nil
		}
	}
	if details.obj == nil || !objIsSpatial(details.obj) {
		return nil
	}
	if details.command == "fset" {
		sw.mu.Lock()
		nofields := sw.nofields
		sw.mu.Unlock()
		if nofields {
			return nil
		}
	}
	if details.command == "del" {
		if fence.roam.on {
			if fence.roam.nearbys != nil {
				delete(fence.roam.nearbys, details.id)
				if len(fence.roam.nearbys) == 0 {
					fence.roam.nearbys = nil
				}
			}
		}
		return []string{
			`{"command":"del"` + hookJSONString(hookName, metas) +
				`,"id":` + jsonString(details.id) +
				`,"time":` + jsonTimeFormat(details.timestamp) + `}`,
		}
	}
	var roamNearbys, roamFaraways []roamMatch
	var detect = "outside"
	if fence != nil {
		if fence.roam.on {
			if details.command == "set" {
				roamNearbys, roamFaraways =
					fenceMatchRoam(sw.c, fence, details.key,
						details.id, details.obj)
			}
			if len(roamNearbys) == 0 && len(roamFaraways) == 0 {
				return nil
			}
			detect = "roam"
		} else {
			// not using roaming
			match1 := fenceMatchObject(fence, details.oldObj)
			match2 := fenceMatchObject(fence, details.obj)
			if match1 && match2 {
				detect = "inside"
			} else if match1 && !match2 {
				detect = "exit"
			} else if !match1 && match2 {
				detect = "enter"
				if details.command == "fset" {
					detect = "inside"
				}
			} else {
				if details.command != "fset" {
					// Maybe the old object and new object create a line that crosses the fence.
					// Must detect for that possibility.
					if details.oldObj != nil {
						ls := geojson.NewLineString(geometry.NewLine(
							[]geometry.Point{
								details.oldObj.Center(),
								details.obj.Center(),
							}, nil))
						temp := false
						if fence.cmd == "within" {
							// because we are testing if the line croses the area we need to use
							// "intersects" instead of "within".
							fence.cmd = "intersects"
							temp = true
						}
						if fenceMatchObject(fence, ls) {
							detect = "cross"
						}
						if temp {
							fence.cmd = "within"
						}
					}
				}
			}
		}
	}

	if details.fmap == nil {
		return nil
	}
	for {
		if fence.detect != nil && !fence.detect[detect] {
			if detect == "enter" {
				detect = "inside"
				continue
			}
			if detect == "exit" {
				detect = "outside"
				continue
			}
			return nil
		}
		break
	}
	sw.mu.Lock()
	var distance float64
	if fence.distance {
		distance = details.obj.Distance(fence.obj)
	}
	sw.fmap = details.fmap
	sw.fullFields = true
	sw.msg.OutputType = JSON
	sw.writeObject(ScanWriterParams{
		id:       details.id,
		o:        details.obj,
		fields:   details.fields,
		noLock:   true,
		distance: distance,
	})

	if sw.wr.Len() == 0 {
		sw.mu.Unlock()
		return nil
	}

	res := sw.wr.String()
	sw.wr.Reset()
	if len(res) > 0 && res[0] == ',' {
		res = res[1:]
	}
	if sw.output == outputIDs {
		res = `{"id":` + string(res) + `}`
	}
	sw.mu.Unlock()

	if fence.groups == nil {
		fence.groups = make(map[string]string)
	}
	groupkey := details.key + ":" + details.id
	var group string
	var ok bool
	if detect == "enter" {
		group = bsonID()
		fence.groups[groupkey] = group
	} else if detect == "cross" {
		group = bsonID()
		delete(fence.groups, groupkey)
	} else {
		group, ok = fence.groups[groupkey]
		if !ok {
			group = bsonID()
			fence.groups[groupkey] = group
		}
	}
	var msgs []string
	if fence.detect == nil || fence.detect[detect] {
		if len(res) > 0 && res[0] == '{' {
			msgs = append(msgs, makemsg(details.command, group, detect,
				hookName, metas, details.key, details.timestamp, res[1:]))
		} else {
			msgs = append(msgs, string(res))
		}
	}
	switch detect {
	case "enter":
		if fence.detect == nil || fence.detect["inside"] {
			msgs = append(msgs, makemsg(details.command, group, "inside", hookName, metas, details.key, details.timestamp, res[1:]))
		}
	case "exit", "cross":
		if fence.detect == nil || fence.detect["outside"] {
			msgs = append(msgs, makemsg(details.command, group, "outside", hookName, metas, details.key, details.timestamp, res[1:]))
		}
	case "roam":
		if len(msgs) > 0 {
			var nmsgs []string
			for i := range roamNearbys {
				nmsg := extendRoamMessage(sw, fence,
					"nearby", msgs[0], roamNearbys[i])
				nmsgs = append(nmsgs, string(nmsg))
			}
			for i := range roamFaraways {
				nmsg := extendRoamMessage(sw, fence,
					"faraway", msgs[0], roamFaraways[i])
				nmsgs = append(nmsgs, string(nmsg))
			}
			msgs = nmsgs
		}
	}
	return msgs
}

func extendRoamMessage(
	sw *scanWriter, fence *liveFenceSwitches,
	kind string, baseMsg string, match roamMatch,
) string {
	// hack off the last '}'
	nmsg := []byte(baseMsg[:len(baseMsg)-1])
	nmsg = append(nmsg, `,"`+kind+`":{"key":`...)
	nmsg = appendJSONString(nmsg, fence.roam.key)
	nmsg = append(nmsg, `,"id":`...)
	nmsg = appendJSONString(nmsg, match.id)
	nmsg = append(nmsg, `,"object":`...)
	nmsg = match.obj.AppendJSON(nmsg)
	nmsg = append(nmsg, `,"meters":`...)
	nmsg = strconv.AppendFloat(nmsg,
		math.Floor(match.meters*1000)/1000, 'f', -1, 64)
	if fence.roam.scan != "" {
		nmsg = append(nmsg, `,"scan":[`...)
		col := sw.c.getCol(fence.roam.key)
		if col != nil {
			obj, _, ok := col.Get(match.id)
			if ok {
				nmsg = append(nmsg, `{"id":`...)
				nmsg = appendJSONString(nmsg, match.id)
				nmsg = append(nmsg, `,"self":true,"object":`...)
				nmsg = obj.AppendJSON(nmsg)
				nmsg = append(nmsg, '}')
			}
			pattern := match.id + fence.roam.scan
			iterator := func(
				oid string, o geojson.Object, fields []float64,
			) bool {
				if oid == match.id {
					return true
				}
				if matched, _ := glob.Match(pattern, oid); matched {
					nmsg = append(nmsg, `,{"id":`...)
					nmsg = appendJSONString(nmsg, oid)
					nmsg = append(nmsg, `,"object":`...)
					nmsg = o.AppendJSON(nmsg)
					nmsg = append(nmsg, '}')
				}
				return true
			}
			g := glob.Parse(pattern, false)
			if g.Limits[0] == "" && g.Limits[1] == "" {
				col.Scan(false, nil, iterator)
			} else {
				col.ScanRange(g.Limits[0], g.Limits[1],
					false, nil, iterator)
			}
		}
		nmsg = append(nmsg, ']')
	}

	nmsg = append(nmsg, '}')

	// re-add the last '}'
	nmsg = append(nmsg, '}')
	return string(nmsg)
}

func makemsg(
	command, group, detect, hookName string,
	metas []FenceMeta, key string, t time.Time, tail string,
) string {
	var buf []byte
	buf = append(append(buf, `{"command":"`...), command...)
	buf = append(append(buf, `","group":"`...), group...)
	buf = append(append(buf, `","detect":"`...), detect...)
	buf = append(buf, '"')
	buf = appendHookDetails(buf, hookName, metas)
	buf = appendJSONString(append(buf, `,"key":`...), key)
	buf = appendJSONTimeFormat(append(buf, `,"time":`...), t)
	buf = append(append(buf, ','), tail...)
	return string(buf)
}

func fenceMatchObject(fence *liveFenceSwitches, obj geojson.Object) bool {
	gobj, _ := obj.(geojson.Object)
	if gobj == nil {
		return false
	}
	if fence.roam.on {
		// we need to check this object against
		return false
	}
	switch fence.cmd {
	case "nearby":
		// nearby is an INTERSECT on a Circle
		return gobj.Intersects(fence.obj)
	case "within":
		return gobj.Within(fence.obj)
	case "intersects":
		return gobj.Intersects(fence.obj)
	}
	return false
}

func fenceMatchRoam(
	c *Server, fence *liveFenceSwitches,
	tkey, tid string, obj geojson.Object,
) (nearbys, faraways []roamMatch) {

	col := c.getCol(fence.roam.key)
	if col == nil {
		return
	}
	prevNearbys := fence.roam.nearbys[tid]
	var newNearbys map[string]bool

	col.Intersects(obj, 0, nil, func(
		id string, obj2 geojson.Object, fields []float64,
	) bool {
		if c.hasExpired(fence.roam.key, id) {
			return true
		}
		var idMatch bool
		if id == tid {
			return true // skip self
		}
		if fence.roam.pattern {
			idMatch, _ = glob.Match(fence.roam.id, id)
		} else {
			idMatch = fence.roam.id == id
		}
		if !idMatch {
			return true
		}
		if newNearbys == nil {
			newNearbys = make(map[string]bool)
		}
		newNearbys[id] = true
		prev := prevNearbys[id]
		if prev {
			delete(prevNearbys, id)
		}
		match := roamMatch{
			id:     id,
			obj:    obj2,
			meters: obj.Distance(obj2),
		}
		if !prev || !fence.nodwell {
			// brand new "nearby"
			nearbys = append(nearbys, match)
		}
		return true
	})
	for id := range prevNearbys {
		obj2, _, ok := col.Get(id)
		if ok && !c.hasExpired(fence.roam.key, id) {
			faraways = append(faraways, roamMatch{
				id:     id,
				obj:    obj2,
				meters: obj.Distance(obj2),
			})
		}
	}

	if len(newNearbys) == 0 {
		if fence.roam.nearbys != nil {
			delete(fence.roam.nearbys, tid)
			if len(fence.roam.nearbys) == 0 {
				fence.roam.nearbys = nil
			}
		}
	} else {
		if fence.roam.nearbys == nil {
			fence.roam.nearbys = make(map[string]map[string]bool)
		}
		fence.roam.nearbys[tid] = newNearbys
	}
	return
}
<commit_msg>Fix roam fence missing<commit_after>package server

import (
	"math"
	"strconv"
	"time"

	"github.com/tidwall/geojson"
	"github.com/tidwall/geojson/geo"
	"github.com/tidwall/geojson/geometry"
	"github.com/tidwall/gjson"
	"github.com/tidwall/tile38/internal/glob"
)

// FenceMatch executes a fence match returns back json messages for fence detection.
func FenceMatch(hookName string, sw *scanWriter, fence *liveFenceSwitches, metas []FenceMeta, details *commandDetails) []string {
	msgs := fenceMatch(hookName, sw, fence, metas, details)
	if len(fence.accept) == 0 {
		return msgs
	}
	nmsgs := make([]string, 0, len(msgs))
	for _, msg := range msgs {
		if fence.accept[gjson.Get(msg, "command").String()] {
			nmsgs = append(nmsgs, msg)
		}
	}
	return nmsgs
}
func appendHookDetails(b []byte, hookName string, metas []FenceMeta) []byte {
	if len(hookName) > 0 {
		b = append(b, `,"hook":`...)
		b = appendJSONString(b, hookName)
	}
	if len(metas) > 0 {
		b = append(b, `,"meta":{`...)
		for i, meta := range metas {
			if i > 0 {
				b = append(b, ',')
			}
			b = appendJSONString(b, meta.Name)
			b = append(b, ':')
			b = appendJSONString(b, meta.Value)
		}
		b = append(b, '}')
	}
	return b
}

func objIsSpatial(obj geojson.Object) bool {
	_, ok := obj.(geojson.Spatial)
	return ok
}

func hookJSONString(hookName string, metas []FenceMeta) string {
	return string(appendHookDetails(nil, hookName, metas))
}
func fenceMatch(
	hookName string, sw *scanWriter, fence *liveFenceSwitches,
	metas []FenceMeta, details *commandDetails,
) []string {
	if details.command == "drop" {
		return []string{
			`{"command":"drop"` + hookJSONString(hookName, metas) +
				`,"time":` + jsonTimeFormat(details.timestamp) + `}`,
		}
	}
	if len(fence.glob) > 0 && !(len(fence.glob) == 1 && fence.glob[0] == '*') {
		match, _ := glob.Match(fence.glob, details.id)
		if !match {
			return nil
		}
	}
	if details.obj == nil || !objIsSpatial(details.obj) {
		return nil
	}
	if details.command == "fset" {
		sw.mu.Lock()
		nofields := sw.nofields
		sw.mu.Unlock()
		if nofields {
			return nil
		}
	}
	if details.command == "del" {
		if fence.roam.on {
			if fence.roam.nearbys != nil {
				delete(fence.roam.nearbys, details.id)
				if len(fence.roam.nearbys) == 0 {
					fence.roam.nearbys = nil
				}
			}
		}
		return []string{
			`{"command":"del"` + hookJSONString(hookName, metas) +
				`,"id":` + jsonString(details.id) +
				`,"time":` + jsonTimeFormat(details.timestamp) + `}`,
		}
	}
	var roamNearbys, roamFaraways []roamMatch
	var detect = "outside"
	if fence != nil {
		if fence.roam.on {
			if details.command == "set" {
				roamNearbys, roamFaraways =
					fenceMatchRoam(sw.c, fence, details.key,
						details.id, details.obj)
			}
			if len(roamNearbys) == 0 && len(roamFaraways) == 0 {
				return nil
			}
			detect = "roam"
		} else {
			// not using roaming
			match1 := fenceMatchObject(fence, details.oldObj)
			match2 := fenceMatchObject(fence, details.obj)
			if match1 && match2 {
				detect = "inside"
			} else if match1 && !match2 {
				detect = "exit"
			} else if !match1 && match2 {
				detect = "enter"
				if details.command == "fset" {
					detect = "inside"
				}
			} else {
				if details.command != "fset" {
					// Maybe the old object and new object create a line that crosses the fence.
					// Must detect for that possibility.
					if details.oldObj != nil {
						ls := geojson.NewLineString(geometry.NewLine(
							[]geometry.Point{
								details.oldObj.Center(),
								details.obj.Center(),
							}, nil))
						temp := false
						if fence.cmd == "within" {
							// because we are testing if the line croses the area we need to use
							// "intersects" instead of "within".
							fence.cmd = "intersects"
							temp = true
						}
						if fenceMatchObject(fence, ls) {
							detect = "cross"
						}
						if temp {
							fence.cmd = "within"
						}
					}
				}
			}
		}
	}

	if details.fmap == nil {
		return nil
	}
	for {
		if fence.detect != nil && !fence.detect[detect] {
			if detect == "enter" {
				detect = "inside"
				continue
			}
			if detect == "exit" {
				detect = "outside"
				continue
			}
			return nil
		}
		break
	}
	sw.mu.Lock()
	var distance float64
	if fence.distance {
		distance = details.obj.Distance(fence.obj)
	}
	sw.fmap = details.fmap
	sw.fullFields = true
	sw.msg.OutputType = JSON
	sw.writeObject(ScanWriterParams{
		id:       details.id,
		o:        details.obj,
		fields:   details.fields,
		noLock:   true,
		distance: distance,
	})

	if sw.wr.Len() == 0 {
		sw.mu.Unlock()
		return nil
	}

	res := sw.wr.String()
	sw.wr.Reset()
	if len(res) > 0 && res[0] == ',' {
		res = res[1:]
	}
	if sw.output == outputIDs {
		res = `{"id":` + string(res) + `}`
	}
	sw.mu.Unlock()

	if fence.groups == nil {
		fence.groups = make(map[string]string)
	}
	groupkey := details.key + ":" + details.id
	var group string
	var ok bool
	if detect == "enter" {
		group = bsonID()
		fence.groups[groupkey] = group
	} else if detect == "cross" {
		group = bsonID()
		delete(fence.groups, groupkey)
	} else {
		group, ok = fence.groups[groupkey]
		if !ok {
			group = bsonID()
			fence.groups[groupkey] = group
		}
	}
	var msgs []string
	if fence.detect == nil || fence.detect[detect] {
		if len(res) > 0 && res[0] == '{' {
			msgs = append(msgs, makemsg(details.command, group, detect,
				hookName, metas, details.key, details.timestamp, res[1:]))
		} else {
			msgs = append(msgs, string(res))
		}
	}
	switch detect {
	case "enter":
		if fence.detect == nil || fence.detect["inside"] {
			msgs = append(msgs, makemsg(details.command, group, "inside", hookName, metas, details.key, details.timestamp, res[1:]))
		}
	case "exit", "cross":
		if fence.detect == nil || fence.detect["outside"] {
			msgs = append(msgs, makemsg(details.command, group, "outside", hookName, metas, details.key, details.timestamp, res[1:]))
		}
	case "roam":
		if len(msgs) > 0 {
			var nmsgs []string
			for i := range roamNearbys {
				nmsg := extendRoamMessage(sw, fence,
					"nearby", msgs[0], roamNearbys[i])
				nmsgs = append(nmsgs, string(nmsg))
			}
			for i := range roamFaraways {
				nmsg := extendRoamMessage(sw, fence,
					"faraway", msgs[0], roamFaraways[i])
				nmsgs = append(nmsgs, string(nmsg))
			}
			msgs = nmsgs
		}
	}
	return msgs
}

func extendRoamMessage(
	sw *scanWriter, fence *liveFenceSwitches,
	kind string, baseMsg string, match roamMatch,
) string {
	// hack off the last '}'
	nmsg := []byte(baseMsg[:len(baseMsg)-1])
	nmsg = append(nmsg, `,"`+kind+`":{"key":`...)
	nmsg = appendJSONString(nmsg, fence.roam.key)
	nmsg = append(nmsg, `,"id":`...)
	nmsg = appendJSONString(nmsg, match.id)
	nmsg = append(nmsg, `,"object":`...)
	nmsg = match.obj.AppendJSON(nmsg)
	nmsg = append(nmsg, `,"meters":`...)
	nmsg = strconv.AppendFloat(nmsg,
		math.Floor(match.meters*1000)/1000, 'f', -1, 64)
	if fence.roam.scan != "" {
		nmsg = append(nmsg, `,"scan":[`...)
		col := sw.c.getCol(fence.roam.key)
		if col != nil {
			obj, _, ok := col.Get(match.id)
			if ok {
				nmsg = append(nmsg, `{"id":`...)
				nmsg = appendJSONString(nmsg, match.id)
				nmsg = append(nmsg, `,"self":true,"object":`...)
				nmsg = obj.AppendJSON(nmsg)
				nmsg = append(nmsg, '}')
			}
			pattern := match.id + fence.roam.scan
			iterator := func(
				oid string, o geojson.Object, fields []float64,
			) bool {
				if oid == match.id {
					return true
				}
				if matched, _ := glob.Match(pattern, oid); matched {
					nmsg = append(nmsg, `,{"id":`...)
					nmsg = appendJSONString(nmsg, oid)
					nmsg = append(nmsg, `,"object":`...)
					nmsg = o.AppendJSON(nmsg)
					nmsg = append(nmsg, '}')
				}
				return true
			}
			g := glob.Parse(pattern, false)
			if g.Limits[0] == "" && g.Limits[1] == "" {
				col.Scan(false, nil, iterator)
			} else {
				col.ScanRange(g.Limits[0], g.Limits[1],
					false, nil, iterator)
			}
		}
		nmsg = append(nmsg, ']')
	}

	nmsg = append(nmsg, '}')

	// re-add the last '}'
	nmsg = append(nmsg, '}')
	return string(nmsg)
}

func makemsg(
	command, group, detect, hookName string,
	metas []FenceMeta, key string, t time.Time, tail string,
) string {
	var buf []byte
	buf = append(append(buf, `{"command":"`...), command...)
	buf = append(append(buf, `","group":"`...), group...)
	buf = append(append(buf, `","detect":"`...), detect...)
	buf = append(buf, '"')
	buf = appendHookDetails(buf, hookName, metas)
	buf = appendJSONString(append(buf, `,"key":`...), key)
	buf = appendJSONTimeFormat(append(buf, `,"time":`...), t)
	buf = append(append(buf, ','), tail...)
	return string(buf)
}

func fenceMatchObject(fence *liveFenceSwitches, obj geojson.Object) bool {
	gobj, _ := obj.(geojson.Object)
	if gobj == nil {
		return false
	}
	if fence.roam.on {
		// we need to check this object against
		return false
	}
	switch fence.cmd {
	case "nearby":
		// nearby is an INTERSECT on a Circle
		return gobj.Intersects(fence.obj)
	case "within":
		return gobj.Within(fence.obj)
	case "intersects":
		return gobj.Intersects(fence.obj)
	}
	return false
}

func fenceMatchRoam(
	c *Server, fence *liveFenceSwitches,
	tkey, tid string, obj geojson.Object,
) (nearbys, faraways []roamMatch) {
	col := c.getCol(fence.roam.key)
	if col == nil {
		return
	}
	prevNearbys := fence.roam.nearbys[tid]
	var newNearbys map[string]bool
	center := obj.Center()
	minLat, minLon, maxLat, maxLon :=
		geo.RectFromCenter(center.Y, center.X, fence.roam.meters)
	rect := geometry.Rect{
		Min: geometry.Point{X: minLon, Y: minLat},
		Max: geometry.Point{X: maxLon, Y: maxLat},
	}
	col.Intersects(geojson.NewRect(rect), 0, nil, func(
		id string, obj2 geojson.Object, fields []float64,
	) bool {
		if c.hasExpired(fence.roam.key, id) {
			return true
		}
		var idMatch bool
		if id == tid {
			return true // skip self
		}
		if fence.roam.pattern {
			idMatch, _ = glob.Match(fence.roam.id, id)
		} else {
			idMatch = fence.roam.id == id
		}
		if !idMatch {
			return true
		}
		if newNearbys == nil {
			newNearbys = make(map[string]bool)
		}
		newNearbys[id] = true
		prev := prevNearbys[id]
		if prev {
			delete(prevNearbys, id)
		}
		match := roamMatch{
			id:     id,
			obj:    obj2,
			meters: obj.Distance(obj2),
		}

		if !prev || !fence.nodwell {
			// brand new "nearby"
			nearbys = append(nearbys, match)
		}
		return true
	})
	for id := range prevNearbys {
		obj2, _, ok := col.Get(id)
		if ok && !c.hasExpired(fence.roam.key, id) {
			faraways = append(faraways, roamMatch{
				id:     id,
				obj:    obj2,
				meters: obj.Distance(obj2),
			})
		}
	}

	if len(newNearbys) == 0 {
		if fence.roam.nearbys != nil {
			delete(fence.roam.nearbys, tid)
			if len(fence.roam.nearbys) == 0 {
				fence.roam.nearbys = nil
			}
		}
	} else {
		if fence.roam.nearbys == nil {
			fence.roam.nearbys = make(map[string]map[string]bool)
		}
		fence.roam.nearbys[tid] = newNearbys
	}
	return
}
<|endoftext|>


<commit_before>package glisp
func init() { GITLASTTAG = "0.4.0"; GITLASTCOMMIT = "8225b71235f9efd1cd38f9a3743f8983aec070c2" }
<commit_msg>tagged v0.5.0<commit_after>package glisp
func init() { GITLASTTAG = "v0.5.0"; GITLASTCOMMIT = "fa88edeb545036ffaf992375969784d0c2774901" }
<|endoftext|>


<commit_before>package os

import (
	"errors"
	"io/ioutil"
	"strings"
	"sync"
)

var (
	// osReleaseFile is the name of the file that is read in order to determine
	// the linux type release version.
	osReleaseFile = "/etc/os-release"
	osOnce        sync.Once
	os            OSType // filled in by the first call to hostOS
)

func hostOS() OSType {
	osOnce.Do(func() {
		var err error
		os, err = updateOS(osReleaseFile)
		if err != nil {
			panic("unable to read " + osReleaseFile + ": " + err.Error())
		}
	})
	return os
}

var defaultVersionIDs = map[string]string{
	"arch": "rolling",
}

func updateOS(f string) (OSType, error) {
	values, err := ReadOSRelease(f)
	if err != nil {
		return Unknown, err
	}
	switch values["ID"] {
	case strings.ToLower(Ubuntu.String()):
		return Ubuntu, nil
	case strings.ToLower(Arch.String()):
		return Arch, nil
	case strings.ToLower(CentOS.String()):
		return CentOS, nil
	case strings.ToLower(Debian.String()):
		return Debian, nil
	default:
		return Unknown, nil
	}
}

// ReadOSRelease parses the information in the os-release file.
//
// See http://www.freedesktop.org/software/systemd/man/os-release.html.
func ReadOSRelease(f string) (map[string]string, error) {
	contents, err := ioutil.ReadFile(f)
	if err != nil {
		return nil, err
	}
	values := make(map[string]string)
	releaseDetails := strings.Split(string(contents), "\n")
	for _, val := range releaseDetails {
		c := strings.SplitN(val, "=", 2)
		if len(c) != 2 {
			continue
		}
		values[c[0]] = strings.Trim(c[1], "\t '\"")
	}
	id, ok := values["ID"]
	if !ok {
		return nil, errors.New("OS release file is missing ID")
	}
	if _, ok := values["VERSION_ID"]; !ok {
		values["VERSION_ID"], ok = defaultVersionIDs[id]
		if !ok {
			return nil, errors.New("OS release file is missing VERSION_ID")
		}
	}
	return values, nil
}
<commit_msg>commit<commit_after>package os

import (
	"errors"
	"io/ioutil"
	"strings"
	"sync"
)

var (
	// osReleaseFile is the name of the file that is read in order to determine
	// the linux type release version.
	osReleaseFile = "/etc/os-release"
	osFile				= "/etc/redhat-release"
	osOnce        sync.Once
	os            OSType // filled in by the first call to hostOS
)

func hostOS() OSType {
	osOnce.Do(func() {
		var err error
		os, err = updateOS(osReleaseFile)
		if err != nil {
			panic("unable to read " + osReleaseFile + ": " + err.Error())
		}
	})
	return os
}

var defaultVersionIDs = map[string]string{
	"arch": "rolling",
}

func updateOS(f string) (OSType, error) {
values := make(map[string]string)
if _, err := ioutil.ReadFile(f); err == nil {
	values, err = ReadOSRelease(f)
	if err != nil {
		return Unknown, err
	}
}else{
	values, err = ReadRelease(osFile)
	if err != nil{
		return Unknown, err
	}
}
	switch values["ID"] {
	case strings.ToLower(Ubuntu.String()):
		return Ubuntu, nil
	case strings.ToLower(Arch.String()):
		return Arch, nil
	case strings.ToLower(CentOS.String()):
		return CentOS, nil
	case strings.ToLower(Debian.String()):
		return Debian, nil
	default:
		return Unknown, nil
	}
}

// ReadOSRelease parses the information in the os-release file.
//
// See http://www.freedesktop.org/software/systemd/man/os-release.html.
func ReadOSRelease(f string) (map[string]string, error) {
	contents, err := ioutil.ReadFile(f)
	if err != nil {
		return nil, err
	}
	values := make(map[string]string)
	releaseDetails := strings.Split(string(contents), "\n")
	for _, val := range releaseDetails {
		c := strings.SplitN(val, "=", 2)
		if len(c) != 2 {
			continue
		}
		values[c[0]] = strings.Trim(c[1], "\t '\"")
	}
	id, ok := values["ID"]
	if !ok {
		return nil, errors.New("OS release file is missing ID")
	}
	if _, ok := values["VERSION_ID"]; !ok {
		values["VERSION_ID"], ok = defaultVersionIDs[id]
		if !ok {
			return nil, errors.New("OS release file is missing VERSION_ID")
		}
	}
	return values, nil
}

// ReadRelease parses the information in the redhat-release file.

func ReadRelease (f string) (map[string]string, error){
	stream, err := ioutil.ReadFile(osFile);
	if err != nil {
		return nil, err
	}
	values := make(map[string]string)
	releaseDetail := strings.Split(string(stream)," ")
	values["ID"] = releaseDetail[0]
	values["VERSION_ID"] = releaseDetail[2]
	id, ok := values["ID"]
	if !ok {
		return nil, errors.New("OS release file is missing ID")
	}
	if _, ok := values["VERSION_ID"]; !ok {
		values["VERSION_ID"], ok = defaultVersionIDs[id]
		if !ok {
			return nil, errors.New("OS release file is missing VERSION_ID")
		}
	}
	return values, nil
}
<|endoftext|>


<commit_before>package mysql

import (
	"database/sql"
	"strings"
	"testing"
)

var (
	// dsn from driver_test.go
	sample = []byte(strings.Repeat("0123456789abcdef", 1024*1024))
)

func BenchmarkRoundtripText(b *testing.B) {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		b.Fatalf("crashed")
	}
	defer db.Close()
	var result string
	for i := 0; i < b.N; i++ {
		length := 16 + i%(4*b.N)
		test := string(sample[0:length])
		rows, err := db.Query("SELECT \"" + test + "\"")
		if err != nil {
			b.Fatalf("crashed")
		}
		if !rows.Next() {
			rows.Close()
			b.Fatalf("crashed")
		}
		err = rows.Scan(&result)
		if err != nil {
			rows.Close()
			b.Fatalf("crashed")
		}
		if result != test {
			rows.Close()
			b.Errorf("mismatch")
		}
		rows.Close()
	}
}

func BenchmarkRoundtripPrepared(b *testing.B) {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		b.Fatalf("crashed")
	}
	defer db.Close()
	var result string
	stmt, err := db.Prepare("SELECT ?")
	if err != nil {
		b.Fatalf("crashed")
	}
	for i := 0; i < b.N; i++ {
		length := 16 + i%(4*b.N)
		test := string(sample[0:length])
		rows, err := stmt.Query(test)
		if err != nil {
			b.Fatalf("crashed")
		}
		if !rows.Next() {
			rows.Close()
			b.Fatalf("crashed")
		}
		err = rows.Scan(&result)
		if err != nil {
			rows.Close()
			b.Fatalf("crashed")
		}
		if result != test {
			rows.Close()
			b.Errorf("mismatch")
		}
		rows.Close()
	}
}
<commit_msg>use sane value for length of string selected; merged with current master<commit_after>package mysql

import (
	"database/sql"
	"strings"
	"testing"
)

var (
	// dsn from driver_test.go
	sample   = []byte(strings.Repeat("0123456789abcdef", 1024*1024))
	min, max = 16, len(sample)
)

func BenchmarkRoundtripText(b *testing.B) {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		b.Fatalf("crashed")
	}
	defer db.Close()
	var result string
	for i := 0; i < b.N; i++ {
		length := min + i
		if length > max {
			length = max
		}
		test := string(sample[0:length])
		rows, err := db.Query("SELECT \"" + test + "\"")
		if err != nil {
			b.Fatalf("crashed")
		}
		if !rows.Next() {
			rows.Close()
			b.Fatalf("crashed")
		}
		err = rows.Scan(&result)
		if err != nil {
			rows.Close()
			b.Fatalf("crashed")
		}
		if result != test {
			rows.Close()
			b.Errorf("mismatch")
		}
		rows.Close()
	}
}

func BenchmarkRoundtripPrepared(b *testing.B) {
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		b.Fatalf("crashed")
	}
	defer db.Close()
	var result string
	stmt, err := db.Prepare("SELECT ?")
	if err != nil {
		b.Fatalf("crashed")
	}
	for i := 0; i < b.N; i++ {
		length := min + i
		if length > max {
			length = max
		}
		test := string(sample[0:length])
		rows, err := stmt.Query(test)
		if err != nil {
			b.Fatalf("crashed")
		}
		if !rows.Next() {
			rows.Close()
			b.Fatalf("crashed")
		}
		err = rows.Scan(&result)
		if err != nil {
			rows.Close()
			b.Fatalf("crashed")
		}
		if result != test {
			rows.Close()
			b.Errorf("mismatch")
		}
		rows.Close()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/HouzuoGuo/tiedot/db"
	"math/rand"
	"os"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

var benchTestSize = 50000

func averageTest(name string, fun func()) {
	numThreads := runtime.GOMAXPROCS(-1)
	wp := new(sync.WaitGroup)
	iter := float64(benchTestSize)
	start := float64(time.Now().UTC().UnixNano())
	// Run function across multiple goroutines
	for i := 0; i < benchTestSize; i += benchTestSize / numThreads {
		wp.Add(1)
		go func() {
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads; j++ {
				fun()
			}
		}()
	}
	wp.Wait()
	end := float64(time.Now().UTC().UnixNano())
	fmt.Printf("%s %d: %d ns/iter, %d iter/sec\n", name, int(benchTestSize), int((end-start)/iter), int(1000000000/((end-start)/iter)))
}

// benchmark(1) written in test case style
func TestBenchmark1(t *testing.T) {
	return
	runtime.GOMAXPROCS(runtime.NumCPU())
	rand.Seed(time.Now().UnixNano())

	ids := make([]int, 0, benchTestSize)
	// Prepare a collection with two indexes
	tmp := "/tmp/tiedot_test_bench1"
	os.RemoveAll(tmp)
	defer os.RemoveAll(tmp)
	benchDB, col := mkTmpDBAndCol(tmp, "tmp")
	defer benchDB.Close()
	col.Index([]string{"nested", "nested", "str"})
	col.Index([]string{"nested", "nested", "int"})
	col.Index([]string{"nested", "nested", "float"})
	col.Index([]string{"strs"})
	col.Index([]string{"ints"})
	col.Index([]string{"floats"})

	// Benchmark document insert
	average("insert", func() {
		if _, err := col.Insert(sampleDoc()); err != nil {
			panic(err)
		}
	})

	// Collect all document IDs and benchmark document read
	col.ForEachDoc(func(id int, _ []byte) bool {
		ids = append(ids, id)
		return true
	})
	average("read", func() {
		doc, err := col.Read(ids[rand.Intn(benchTestSize)])
		if doc == nil || err != nil {
			panic(err)
		}
	})

	// Benchmark lookup query (two attributes)
	average("lookup", func() {
		result := make(map[int]struct{})
		if err := db.EvalQuery(sampleQuery(), col, &result); err != nil {
			panic(err)
		}
	})

	// Benchmark document update
	average("update", func() {
		if err := col.Update(ids[rand.Intn(benchTestSize)], sampleDoc()); err != nil && !strings.Contains(err.Error(), "locked") {
			panic(err)
		}
	})

	// Benchmark document delete
	var delCount int64
	average("delete", func() {
		if err := col.Delete(ids[rand.Intn(benchTestSize)]); err == nil {
			atomic.AddInt64(&delCount, 1)
		}
	})
	if delCount < int64(benchTestSize/2) {
		t.Fatal("Did not delete enough")
	}
}

// benchmark2 written in test case style
func TestBenchmark2(t *testing.T) {
	return
	runtime.GOMAXPROCS(runtime.NumCPU())
	rand.Seed(time.Now().UnixNano())

	docs := make([]int, 0, benchTestSize*2+1000)
	wp := new(sync.WaitGroup)
	numThreads := runtime.GOMAXPROCS(-1)
	// There are goroutines doing document operations: insert, read, query, update, delete
	wp.Add(5 * numThreads)
	// And one more changing schema and stuff
	wp.Add(1)

	// Prepare a collection with two indexes
	tmp := "/tmp/tiedot_test_bench2"
	os.RemoveAll(tmp)
	defer os.RemoveAll(tmp)
	benchdb, col := mkTmpDBAndCol(tmp, "tmp")
	defer benchdb.Close()
	col.Index([]string{"nested", "nested", "str"})
	col.Index([]string{"nested", "nested", "int"})
	col.Index([]string{"nested", "nested", "float"})
	col.Index([]string{"strs"})
	col.Index([]string{"ints"})
	col.Index([]string{"floats"})

	// Insert 1000 documents to make a start
	for j := 0; j < 1000; j++ {
		if newID, err := col.Insert(sampleDoc()); err == nil {
			docs = append(docs, newID)
		} else {
			panic(err)
		}
	}
	start := float64(time.Now().UTC().UnixNano())

	// Insert benchTestSize * 2 documents
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Insert thread %d starting\n", i)
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads*2; j++ {
				if newID, err := col.Insert(sampleDoc()); err == nil {
					docs = append(docs, newID)
				} else {
					panic(err)
				}
			}
			fmt.Printf("Insert thread %d completed\n", i)
		}(i)
	}

	// Read benchTestSize * 2 documents
	var readCount int64
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Read thread %d starting\n", i)
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads*2; j++ {
				if _, err := col.Read(docs[rand.Intn(len(docs))]); err == nil {
					atomic.AddInt64(&readCount, 1)
				}
			}
			fmt.Printf("Read thread %d completed\n", i)
		}(i)
	}

	// Query benchTestSize times (lookup on two attributes)
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Query thread %d starting\n", i)
			defer wp.Done()
			var err error
			for j := 0; j < benchTestSize/numThreads; j++ {
				result := make(map[int]struct{})
				if err = db.EvalQuery(sampleQuery(), col, &result); err != nil {
					panic(err)
				}
			}
			fmt.Printf("Query thread %d completed\n", i)
		}(i)
	}

	// Update benchTestSize documents
	var updateCount int64
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Update thread %d starting\n", i)
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads; j++ {
				if err := col.Update(docs[rand.Intn(len(docs))], sampleDoc()); err == nil {
					atomic.AddInt64(&updateCount, 1)
				}
			}
			fmt.Printf("Update thread %d completed\n", i)
		}(i)
	}

	// Delete benchTestSize documents
	var delCount int64
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Delete thread %d starting\n", i)
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads; j++ {
				if err := col.Delete(docs[rand.Intn(len(docs))]); err == nil {
					atomic.AddInt64(&delCount, 1)
				}
			}
			fmt.Printf("Delete thread %d completed\n", i)
		}(i)
	}

	// This one does a bunch of schema-changing stuff, testing the engine while document operations are busy
	go func() {
		time.Sleep(500 * time.Millisecond)
		if err := benchdb.Create("foo"); err != nil {
			panic(err)
		} else if err := benchdb.Rename("foo", "bar"); err != nil {
			panic(err)
		} else if err := benchdb.Truncate("bar"); err != nil {
			panic(err)
		} else if err := benchdb.Scrub("bar"); err != nil {
			panic(err)
		} else if benchdb.Use("bar") == nil {
			panic("Missing collection")
		}
		for _, colName := range benchdb.AllCols() {
			if colName != "bar" && colName != "tmp" {
				panic("Wrong collections in benchmark db")
			}
		}
		os.RemoveAll("/tmp/tiedot_test_bench2_dump")
		defer os.RemoveAll("/tmp/tiedot_test_bench2_dump")
		if err := benchdb.Dump("/tmp/tiedot_test_bench2_dump"); err != nil {
			panic(err)
		} else if err := benchdb.Drop("bar"); err != nil {
			panic(err)
		}
		defer wp.Done()
	}()

	// Wait for all goroutines to finish, then print summary
	wp.Wait()
	end := float64(time.Now().UTC().UnixNano())
	fmt.Printf("Total operations %d: %d ns/iter, %d iter/sec\n", benchTestSize*7, int((end-start)/float64(benchTestSize)/7), int(1000000000/((end-start)/float64(benchTestSize)/7)))
	fmt.Printf("Read %d documents\n", readCount)
	fmt.Printf("Updated %d documents\n", updateCount)
	fmt.Printf("Deleted %d documents\n", delCount)
	if readCount < int64(benchTestSize/3) {
		t.Fatal("Did not read enough documents")
	}
	if updateCount < int64(benchTestSize/8) {
		t.Fatal("Did not update enough documents")
	}
	if delCount < int64(benchTestSize/8) {
		t.Fatal("Did not delete enough documents")
	}
}
<commit_msg>reenable benchmark test cases<commit_after>package main

import (
	"fmt"
	"github.com/HouzuoGuo/tiedot/db"
	"math/rand"
	"os"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

var benchTestSize = 50000

func averageTest(name string, fun func()) {
	numThreads := runtime.GOMAXPROCS(-1)
	wp := new(sync.WaitGroup)
	iter := float64(benchTestSize)
	start := float64(time.Now().UTC().UnixNano())
	// Run function across multiple goroutines
	for i := 0; i < benchTestSize; i += benchTestSize / numThreads {
		wp.Add(1)
		go func() {
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads; j++ {
				fun()
			}
		}()
	}
	wp.Wait()
	end := float64(time.Now().UTC().UnixNano())
	fmt.Printf("%s %d: %d ns/iter, %d iter/sec\n", name, int(benchTestSize), int((end-start)/iter), int(1000000000/((end-start)/iter)))
}

// benchmark(1) written in test case style
func TestBenchmark1(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())
	rand.Seed(time.Now().UnixNano())

	ids := make([]int, 0, benchTestSize)
	// Prepare a collection with two indexes
	tmp := "/tmp/tiedot_test_bench1"
	os.RemoveAll(tmp)
	defer os.RemoveAll(tmp)
	benchDB, col := mkTmpDBAndCol(tmp, "tmp")
	defer benchDB.Close()
	col.Index([]string{"nested", "nested", "str"})
	col.Index([]string{"nested", "nested", "int"})
	col.Index([]string{"nested", "nested", "float"})
	col.Index([]string{"strs"})
	col.Index([]string{"ints"})
	col.Index([]string{"floats"})

	// Benchmark document insert
	average("insert", func() {
		if _, err := col.Insert(sampleDoc()); err != nil {
			panic(err)
		}
	})

	// Collect all document IDs and benchmark document read
	col.ForEachDoc(func(id int, _ []byte) bool {
		ids = append(ids, id)
		return true
	})
	average("read", func() {
		doc, err := col.Read(ids[rand.Intn(benchTestSize)])
		if doc == nil || err != nil {
			panic(err)
		}
	})

	// Benchmark lookup query (two attributes)
	average("lookup", func() {
		result := make(map[int]struct{})
		if err := db.EvalQuery(sampleQuery(), col, &result); err != nil {
			panic(err)
		}
	})

	// Benchmark document update
	average("update", func() {
		if err := col.Update(ids[rand.Intn(benchTestSize)], sampleDoc()); err != nil && !strings.Contains(err.Error(), "locked") {
			panic(err)
		}
	})

	// Benchmark document delete
	var delCount int64
	average("delete", func() {
		if err := col.Delete(ids[rand.Intn(benchTestSize)]); err == nil {
			atomic.AddInt64(&delCount, 1)
		}
	})
	if delCount < int64(benchTestSize/2) {
		t.Fatal("Did not delete enough")
	}
}

// benchmark2 written in test case style
func TestBenchmark2(t *testing.T) {
	runtime.GOMAXPROCS(runtime.NumCPU())
	rand.Seed(time.Now().UnixNano())

	docs := make([]int, 0, benchTestSize*2+1000)
	wp := new(sync.WaitGroup)
	numThreads := runtime.GOMAXPROCS(-1)
	// There are goroutines doing document operations: insert, read, query, update, delete
	wp.Add(5 * numThreads)
	// And one more changing schema and stuff
	wp.Add(1)

	// Prepare a collection with two indexes
	tmp := "/tmp/tiedot_test_bench2"
	os.RemoveAll(tmp)
	defer os.RemoveAll(tmp)
	benchdb, col := mkTmpDBAndCol(tmp, "tmp")
	defer benchdb.Close()
	col.Index([]string{"nested", "nested", "str"})
	col.Index([]string{"nested", "nested", "int"})
	col.Index([]string{"nested", "nested", "float"})
	col.Index([]string{"strs"})
	col.Index([]string{"ints"})
	col.Index([]string{"floats"})

	// Insert 1000 documents to make a start
	for j := 0; j < 1000; j++ {
		if newID, err := col.Insert(sampleDoc()); err == nil {
			docs = append(docs, newID)
		} else {
			panic(err)
		}
	}
	start := float64(time.Now().UTC().UnixNano())

	// Insert benchTestSize * 2 documents
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Insert thread %d starting\n", i)
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads*2; j++ {
				if newID, err := col.Insert(sampleDoc()); err == nil {
					docs = append(docs, newID)
				} else {
					panic(err)
				}
			}
			fmt.Printf("Insert thread %d completed\n", i)
		}(i)
	}

	// Read benchTestSize * 2 documents
	var readCount int64
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Read thread %d starting\n", i)
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads*2; j++ {
				if _, err := col.Read(docs[rand.Intn(len(docs))]); err == nil {
					atomic.AddInt64(&readCount, 1)
				}
			}
			fmt.Printf("Read thread %d completed\n", i)
		}(i)
	}

	// Query benchTestSize times (lookup on two attributes)
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Query thread %d starting\n", i)
			defer wp.Done()
			var err error
			for j := 0; j < benchTestSize/numThreads; j++ {
				result := make(map[int]struct{})
				if err = db.EvalQuery(sampleQuery(), col, &result); err != nil {
					panic(err)
				}
			}
			fmt.Printf("Query thread %d completed\n", i)
		}(i)
	}

	// Update benchTestSize documents
	var updateCount int64
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Update thread %d starting\n", i)
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads; j++ {
				if err := col.Update(docs[rand.Intn(len(docs))], sampleDoc()); err == nil {
					atomic.AddInt64(&updateCount, 1)
				}
			}
			fmt.Printf("Update thread %d completed\n", i)
		}(i)
	}

	// Delete benchTestSize documents
	var delCount int64
	for i := 0; i < numThreads; i++ {
		go func(i int) {
			fmt.Printf("Delete thread %d starting\n", i)
			defer wp.Done()
			for j := 0; j < benchTestSize/numThreads; j++ {
				if err := col.Delete(docs[rand.Intn(len(docs))]); err == nil {
					atomic.AddInt64(&delCount, 1)
				}
			}
			fmt.Printf("Delete thread %d completed\n", i)
		}(i)
	}

	// This one does a bunch of schema-changing stuff, testing the engine while document operations are busy
	go func() {
		time.Sleep(500 * time.Millisecond)
		if err := benchdb.Create("foo"); err != nil {
			panic(err)
		} else if err := benchdb.Rename("foo", "bar"); err != nil {
			panic(err)
		} else if err := benchdb.Truncate("bar"); err != nil {
			panic(err)
		} else if err := benchdb.Scrub("bar"); err != nil {
			panic(err)
		} else if benchdb.Use("bar") == nil {
			panic("Missing collection")
		}
		for _, colName := range benchdb.AllCols() {
			if colName != "bar" && colName != "tmp" {
				panic("Wrong collections in benchmark db")
			}
		}
		os.RemoveAll("/tmp/tiedot_test_bench2_dump")
		defer os.RemoveAll("/tmp/tiedot_test_bench2_dump")
		if err := benchdb.Dump("/tmp/tiedot_test_bench2_dump"); err != nil {
			panic(err)
		} else if err := benchdb.Drop("bar"); err != nil {
			panic(err)
		}
		defer wp.Done()
	}()

	// Wait for all goroutines to finish, then print summary
	wp.Wait()
	end := float64(time.Now().UTC().UnixNano())
	fmt.Printf("Total operations %d: %d ns/iter, %d iter/sec\n", benchTestSize*7, int((end-start)/float64(benchTestSize)/7), int(1000000000/((end-start)/float64(benchTestSize)/7)))
	fmt.Printf("Read %d documents\n", readCount)
	fmt.Printf("Updated %d documents\n", updateCount)
	fmt.Printf("Deleted %d documents\n", delCount)
	if readCount < int64(benchTestSize/3) {
		t.Fatal("Did not read enough documents")
	}
	if updateCount < int64(benchTestSize/8) {
		t.Fatal("Did not update enough documents")
	}
	if delCount < int64(benchTestSize/8) {
		t.Fatal("Did not delete enough documents")
	}
}
<|endoftext|>