<commit_before>package main

import (
	"../nsq"
	"../util/pqueue"
	"bitly/notify"
	"container/heap"
	"errors"
	"log"
	"sync"
	"sync/atomic"
	"time"
)

// the amount of time a worker will wait when idle
const defaultWorkerWait = 250 * time.Millisecond

// Channel represents the concrete type for a NSQ channel (and also
// implements the Queue interface)
//
// There can be many channels per topic and each with there own distinct
// clients subscribed.
//
// Channels maintain all client and message, orchestrating in-flight
// messages, timeouts, requeueing, etc.
type Channel struct {
	sync.RWMutex // embed a r/w mutex

	topicName  string
	name       string
	msgTimeout int64

	backend nsq.BackendQueue

	incomingMessageChan chan *nsq.Message
	memoryMsgChan       chan *nsq.Message
	clientMessageChan   chan *nsq.Message
	exitChan            chan int

	// state tracking
	clients []ClientInterface

	// TODO: these can be DRYd up
	deferredMessages map[string]interface{}
	deferredPQ       pqueue.PriorityQueue
	deferredMutex    sync.Mutex
	inFlightMessages map[string]interface{}
	inFlightPQ       pqueue.PriorityQueue
	inFlightMutex    sync.Mutex

	// stat counters
	requeueCount uint64
	messageCount uint64
	timeoutCount uint64
}

type inFlightMessage struct {
	msg    *nsq.Message
	client ClientInterface
}

// NewChannel creates a new instance of the Channel type and returns a pointer
func NewChannel(topicName string, channelName string, inMemSize int64, dataPath string, maxBytesPerFile int64, syncEvery int64, msgTimeout int64) *Channel {
	// backend names, for uniqueness, automatically include the topic... <topic>:<channel>
	backendName := topicName + ":" + channelName
	c := &Channel{
		topicName:           topicName,
		name:                channelName,
		msgTimeout:          msgTimeout,
		backend:             NewDiskQueue(backendName, dataPath, maxBytesPerFile, syncEvery),
		incomingMessageChan: make(chan *nsq.Message, 5),
		memoryMsgChan:       make(chan *nsq.Message, inMemSize),
		clientMessageChan:   make(chan *nsq.Message),
		exitChan:            make(chan int),
		clients:             make([]ClientInterface, 0, 5),
		inFlightMessages:    make(map[string]interface{}),
		inFlightPQ:          pqueue.New(int(inMemSize / 10)),
		deferredMessages:    make(map[string]interface{}),
		deferredPQ:          pqueue.New(int(inMemSize / 10)),
	}
	go c.router()
	go c.messagePump()
	go c.deferredWorker()
	go c.inFlightWorker()
	notify.Post("new_channel", c)
	return c
}

// Close cleanly closes the Channel
func (c *Channel) Close() error {
	var err error

	log.Printf("CHANNEL(%s): closing", c.name)

	close(c.exitChan)
	FlushQueue(c)

	err = c.backend.Close()
	if err != nil {
		return err
	}

	return nil
}

// MemoryChan implements the Queue interface
func (c *Channel) MemoryChan() chan *nsq.Message {
	return c.memoryMsgChan
}

// BackendQueue implements the Queue interface
func (c *Channel) BackendQueue() nsq.BackendQueue {
	return c.backend
}

// InFlight implements the Queue interface
func (c *Channel) InFlight() map[string]interface{} {
	return c.inFlightMessages
}

// Deferred implements the Queue interface
func (c *Channel) Deferred() map[string]interface{} {
	return c.deferredMessages
}

// PutMessage writes to the appropriate incoming message channel
// (which will be routed asynchronously)
func (c *Channel) PutMessage(msg *nsq.Message) {
	c.incomingMessageChan <- msg
	atomic.AddUint64(&c.messageCount, 1)
}

// FinishMessage successfully discards an in-flight message
func (c *Channel) FinishMessage(id []byte) error {
	item, err := c.popInFlightMessage(id)
	if err != nil {
		log.Printf("ERROR: failed to finish message(%s) - %s", id, err.Error())
	} else {
		c.removeFromInFlightPQ(item)
	}
	return err
}

// RequeueMessage requeues a message based on `time.Duration`, ie:
//
// `timeoutMs` == 0 - requeue a message immediately
// `timeoutMs`  > 0 - asynchronously wait for the specified timeout
//     and requeue a message (aka "deferred requeue")
//
func (c *Channel) RequeueMessage(id []byte, timeout time.Duration) error {
	// remove from inflight first
	item, err := c.popInFlightMessage(id)
	if err != nil {
		return err
	}
	c.removeFromInFlightPQ(item)

	msg := item.Value.(*inFlightMessage).msg

	if timeout == 0 {
		return c.doRequeue(msg)
	}

	// deferred requeue
	return c.StartDeferredTimeout(msg, timeout)
}

// AddClient adds the ServerClient the Channel's client list
func (c *Channel) AddClient(client ClientInterface) {
	c.Lock()
	defer c.Unlock()

	found := false
	for _, cli := range c.clients {
		if cli == client {
			found = true
			break
		}
	}

	if !found {
		c.clients = append(c.clients, client)
	}
}

// RemoveClient removes the ServerClient from the Channel's client list
func (c *Channel) RemoveClient(client ClientInterface) {
	c.Lock()
	defer c.Unlock()

	if len(c.clients) == 0 {
		return
	}

	finalClients := make([]ClientInterface, 0, len(c.clients)-1)
	for _, cli := range c.clients {
		if cli != client {
			finalClients = append(finalClients, cli)
		}
	}

	c.clients = finalClients
}

func (c *Channel) StartInFlightTimeout(msg *nsq.Message, client ClientInterface) error {
	value := &inFlightMessage{msg, client}
	absTs := time.Now().UnixNano() + c.msgTimeout
	item := &pqueue.Item{Value: value, Priority: -absTs}
	err := c.pushInFlightMessage(item)
	if err != nil {
		return err
	}
	c.addToInFlightPQ(item)
	return nil
}

func (c *Channel) StartDeferredTimeout(msg *nsq.Message, timeout time.Duration) error {
	absTs := time.Now().UnixNano() + int64(timeout)
	item := &pqueue.Item{Value: msg, Priority: -absTs}
	err := c.pushDeferredMessage(item)
	if err != nil {
		return err
	}
	c.addToDeferredPQ(item)
	return nil
}

// doRequeue performs the low level operations to requeue a message
func (c *Channel) doRequeue(msg *nsq.Message) error {
	c.incomingMessageChan <- msg
	atomic.AddUint64(&c.requeueCount, 1)
	return nil
}

// pushInFlightMessage atomically adds a message to the in-flight dictionary
func (c *Channel) pushInFlightMessage(item *pqueue.Item) error {
	c.Lock()
	defer c.Unlock()

	id := item.Value.(*inFlightMessage).msg.Id
	_, ok := c.inFlightMessages[string(id)]
	if ok {
		return errors.New("E_ID_ALREADY_IN_FLIGHT")
	}
	c.inFlightMessages[string(id)] = item

	return nil
}

// popInFlightMessage atomically removes a message from the in-flight dictionary
func (c *Channel) popInFlightMessage(id []byte) (*pqueue.Item, error) {
	c.Lock()
	defer c.Unlock()

	item, ok := c.inFlightMessages[string(id)]
	if !ok {
		return nil, errors.New("E_ID_NOT_IN_FLIGHT")
	}
	delete(c.inFlightMessages, string(id))

	return item.(*pqueue.Item), nil
}

func (c *Channel) addToInFlightPQ(item *pqueue.Item) {
	c.inFlightMutex.Lock()
	defer c.inFlightMutex.Unlock()
	heap.Push(&c.inFlightPQ, item)
}

func (c *Channel) removeFromInFlightPQ(item *pqueue.Item) {
	c.inFlightMutex.Lock()
	defer c.inFlightMutex.Unlock()
	heap.Remove(&c.inFlightPQ, item.Index)
}

func (c *Channel) pushDeferredMessage(item *pqueue.Item) error {
	c.Lock()
	defer c.Unlock()

	id := item.Value.(*nsq.Message).Id
	_, ok := c.deferredMessages[string(id)]
	if ok {
		return errors.New("E_ID_ALREADY_DEFERRED")
	}
	c.deferredMessages[string(id)] = item

	return nil
}

func (c *Channel) popDeferredMessage(id []byte) (*pqueue.Item, error) {
	c.Lock()
	defer c.Unlock()

	item, ok := c.deferredMessages[string(id)]
	if !ok {
		return nil, errors.New("E_ID_NOT_DEFERRED")
	}
	delete(c.deferredMessages, string(id))

	return item.(*pqueue.Item), nil
}

func (c *Channel) addToDeferredPQ(item *pqueue.Item) {
	c.deferredMutex.Lock()
	defer c.deferredMutex.Unlock()
	heap.Push(&c.deferredPQ, item)
}

// Router handles the muxing of incoming Channel messages, either writing
// to the in-memory channel or to the backend
func (c *Channel) router() {
	for {
		select {
		case msg := <-c.incomingMessageChan:
			select {
			case c.memoryMsgChan <- msg:
			default:
				err := WriteMessageToBackend(msg, c)
				if err != nil {
					log.Printf("ERROR: failed to write message to backend - %s", err.Error())
					// TODO: requeue?
					continue
				}
			}
		case <-c.exitChan:
			return
		}
	}
}

// messagePump reads messages from either memory or backend and writes
// to the client output go channel
//
// it is also performs in-flight accounting and initiates the auto-requeue
// goroutine
func (c *Channel) messagePump() {
	var msg *nsq.Message
	var buf []byte
	var err error

	for {
		select {
		case msg = <-c.memoryMsgChan:
		case buf = <-c.backend.ReadChan():
			msg, err = nsq.DecodeMessage(buf)
			if err != nil {
				log.Printf("ERROR: failed to decode message - %s", err.Error())
				continue
			}
		case <-c.exitChan:
			return
		}

		msg.Attempts++

		c.clientMessageChan <- msg
		// the client will call back to mark as in-flight w/ it's info
	}
}

func (c *Channel) deferredWorker() {
	pqWorker(&c.deferredPQ, &c.deferredMutex, func(item *pqueue.Item) {
		msg := item.Value.(*nsq.Message)
		_, err := c.popDeferredMessage(msg.Id)
		if err != nil {
			return
		}
		c.doRequeue(msg)
	})
}

func (c *Channel) inFlightWorker() {
	pqWorker(&c.inFlightPQ, &c.inFlightMutex, func(item *pqueue.Item) {
		client := item.Value.(*inFlightMessage).client
		msg := item.Value.(*inFlightMessage).msg
		_, err := c.popInFlightMessage(msg.Id)
		if err != nil {
			return
		}
		atomic.AddUint64(&c.timeoutCount, 1)
		client.TimedOutMessage()
		c.doRequeue(msg)
	})
}

// generic loop (executed in a goroutine) that periodically wakes up to walk
// the specified (chronological) priority queue and call the callback
//
// if the first element on the queue is not ready (not enough time has elapsed)
// the amount of time to wait before the next iteration is adjusted to optimize
//
// TODO: fix edge case where you're waiting and a new element is concurrently
// added that has a lower timeout (ie. added as the first element)
func pqWorker(pq *pqueue.PriorityQueue, mutex *sync.Mutex, callback func(item *pqueue.Item)) {
	waitTime := defaultWorkerWait
	for {
		<-time.After(waitTime)
		now := time.Now().UnixNano()
		for {
			mutex.Lock()
			item, diff := pq.PeekAndShift(-now)
			mutex.Unlock()

			if diff == 0 {
				waitTime = defaultWorkerWait
			} else {
				waitTime = time.Duration(diff) + time.Millisecond
			}

			if item == nil {
				break
			}

			callback(item)
		}
	}
}
<commit_msg>bound the window of time for priority queue work; fix race condition for removal from pqueue<commit_after>package main

import (
	"../nsq"
	"../util/pqueue"
	"bitly/notify"
	"container/heap"
	"errors"
	"log"
	"sync"
	"sync/atomic"
	"time"
)

// the amount of time a worker will wait when idle
const defaultWorkerWait = 250 * time.Millisecond

// Channel represents the concrete type for a NSQ channel (and also
// implements the Queue interface)
//
// There can be many channels per topic and each with there own distinct
// clients subscribed.
//
// Channels maintain all client and message, orchestrating in-flight
// messages, timeouts, requeueing, etc.
type Channel struct {
	sync.RWMutex // embed a r/w mutex

	topicName  string
	name       string
	msgTimeout int64

	backend nsq.BackendQueue

	incomingMessageChan chan *nsq.Message
	memoryMsgChan       chan *nsq.Message
	clientMessageChan   chan *nsq.Message
	exitChan            chan int

	// state tracking
	clients []ClientInterface

	// TODO: these can be DRYd up
	deferredMessages map[string]interface{}
	deferredPQ       pqueue.PriorityQueue
	deferredMutex    sync.Mutex
	inFlightMessages map[string]interface{}
	inFlightPQ       pqueue.PriorityQueue
	inFlightMutex    sync.Mutex

	// stat counters
	requeueCount uint64
	messageCount uint64
	timeoutCount uint64
}

type inFlightMessage struct {
	msg    *nsq.Message
	client ClientInterface
}

// NewChannel creates a new instance of the Channel type and returns a pointer
func NewChannel(topicName string, channelName string, inMemSize int64, dataPath string, maxBytesPerFile int64, syncEvery int64, msgTimeout int64) *Channel {
	// backend names, for uniqueness, automatically include the topic... <topic>:<channel>
	backendName := topicName + ":" + channelName
	c := &Channel{
		topicName:           topicName,
		name:                channelName,
		msgTimeout:          msgTimeout,
		backend:             NewDiskQueue(backendName, dataPath, maxBytesPerFile, syncEvery),
		incomingMessageChan: make(chan *nsq.Message, 5),
		memoryMsgChan:       make(chan *nsq.Message, inMemSize),
		clientMessageChan:   make(chan *nsq.Message),
		exitChan:            make(chan int),
		clients:             make([]ClientInterface, 0, 5),
		inFlightMessages:    make(map[string]interface{}),
		inFlightPQ:          pqueue.New(int(inMemSize / 10)),
		deferredMessages:    make(map[string]interface{}),
		deferredPQ:          pqueue.New(int(inMemSize / 10)),
	}
	go c.router()
	go c.messagePump()
	go c.deferredWorker()
	go c.inFlightWorker()
	notify.Post("new_channel", c)
	return c
}

// Close cleanly closes the Channel
func (c *Channel) Close() error {
	var err error

	log.Printf("CHANNEL(%s): closing", c.name)

	close(c.exitChan)
	FlushQueue(c)

	err = c.backend.Close()
	if err != nil {
		return err
	}

	return nil
}

// MemoryChan implements the Queue interface
func (c *Channel) MemoryChan() chan *nsq.Message {
	return c.memoryMsgChan
}

// BackendQueue implements the Queue interface
func (c *Channel) BackendQueue() nsq.BackendQueue {
	return c.backend
}

// InFlight implements the Queue interface
func (c *Channel) InFlight() map[string]interface{} {
	return c.inFlightMessages
}

// Deferred implements the Queue interface
func (c *Channel) Deferred() map[string]interface{} {
	return c.deferredMessages
}

// PutMessage writes to the appropriate incoming message channel
// (which will be routed asynchronously)
func (c *Channel) PutMessage(msg *nsq.Message) {
	c.incomingMessageChan <- msg
	atomic.AddUint64(&c.messageCount, 1)
}

// FinishMessage successfully discards an in-flight message
func (c *Channel) FinishMessage(id []byte) error {
	item, err := c.popInFlightMessage(id)
	if err != nil {
		log.Printf("ERROR: failed to finish message(%s) - %s", id, err.Error())
	} else {
		c.removeFromInFlightPQ(item)
	}
	return err
}

// RequeueMessage requeues a message based on `time.Duration`, ie:
//
// `timeoutMs` == 0 - requeue a message immediately
// `timeoutMs`  > 0 - asynchronously wait for the specified timeout
//     and requeue a message (aka "deferred requeue")
//
func (c *Channel) RequeueMessage(id []byte, timeout time.Duration) error {
	// remove from inflight first
	item, err := c.popInFlightMessage(id)
	if err != nil {
		return err
	}
	c.removeFromInFlightPQ(item)

	msg := item.Value.(*inFlightMessage).msg

	if timeout == 0 {
		return c.doRequeue(msg)
	}

	// deferred requeue
	return c.StartDeferredTimeout(msg, timeout)
}

// AddClient adds the ServerClient the Channel's client list
func (c *Channel) AddClient(client ClientInterface) {
	c.Lock()
	defer c.Unlock()

	found := false
	for _, cli := range c.clients {
		if cli == client {
			found = true
			break
		}
	}

	if !found {
		c.clients = append(c.clients, client)
	}
}

// RemoveClient removes the ServerClient from the Channel's client list
func (c *Channel) RemoveClient(client ClientInterface) {
	c.Lock()
	defer c.Unlock()

	if len(c.clients) == 0 {
		return
	}

	finalClients := make([]ClientInterface, 0, len(c.clients)-1)
	for _, cli := range c.clients {
		if cli != client {
			finalClients = append(finalClients, cli)
		}
	}

	c.clients = finalClients
}

func (c *Channel) StartInFlightTimeout(msg *nsq.Message, client ClientInterface) error {
	value := &inFlightMessage{msg, client}
	absTs := time.Now().UnixNano() + c.msgTimeout
	item := &pqueue.Item{Value: value, Priority: -absTs}
	err := c.pushInFlightMessage(item)
	if err != nil {
		return err
	}
	c.addToInFlightPQ(item)
	return nil
}

func (c *Channel) StartDeferredTimeout(msg *nsq.Message, timeout time.Duration) error {
	absTs := time.Now().UnixNano() + int64(timeout)
	item := &pqueue.Item{Value: msg, Priority: -absTs}
	err := c.pushDeferredMessage(item)
	if err != nil {
		return err
	}
	c.addToDeferredPQ(item)
	return nil
}

// doRequeue performs the low level operations to requeue a message
func (c *Channel) doRequeue(msg *nsq.Message) error {
	c.incomingMessageChan <- msg
	atomic.AddUint64(&c.requeueCount, 1)
	return nil
}

// pushInFlightMessage atomically adds a message to the in-flight dictionary
func (c *Channel) pushInFlightMessage(item *pqueue.Item) error {
	c.Lock()
	defer c.Unlock()

	id := item.Value.(*inFlightMessage).msg.Id
	_, ok := c.inFlightMessages[string(id)]
	if ok {
		return errors.New("E_ID_ALREADY_IN_FLIGHT")
	}
	c.inFlightMessages[string(id)] = item

	return nil
}

// popInFlightMessage atomically removes a message from the in-flight dictionary
func (c *Channel) popInFlightMessage(id []byte) (*pqueue.Item, error) {
	c.Lock()
	defer c.Unlock()

	item, ok := c.inFlightMessages[string(id)]
	if !ok {
		return nil, errors.New("E_ID_NOT_IN_FLIGHT")
	}
	delete(c.inFlightMessages, string(id))

	return item.(*pqueue.Item), nil
}

func (c *Channel) addToInFlightPQ(item *pqueue.Item) {
	c.inFlightMutex.Lock()
	defer c.inFlightMutex.Unlock()
	heap.Push(&c.inFlightPQ, item)
}

func (c *Channel) removeFromInFlightPQ(item *pqueue.Item) {
	c.inFlightMutex.Lock()
	defer c.inFlightMutex.Unlock()
	if item.Index == -1 {
		// this item has already been Pop'd off the pqueue
		return
	}
	heap.Remove(&c.inFlightPQ, item.Index)
}

func (c *Channel) pushDeferredMessage(item *pqueue.Item) error {
	c.Lock()
	defer c.Unlock()

	id := item.Value.(*nsq.Message).Id
	_, ok := c.deferredMessages[string(id)]
	if ok {
		return errors.New("E_ID_ALREADY_DEFERRED")
	}
	c.deferredMessages[string(id)] = item

	return nil
}

func (c *Channel) popDeferredMessage(id []byte) (*pqueue.Item, error) {
	c.Lock()
	defer c.Unlock()

	item, ok := c.deferredMessages[string(id)]
	if !ok {
		return nil, errors.New("E_ID_NOT_DEFERRED")
	}
	delete(c.deferredMessages, string(id))

	return item.(*pqueue.Item), nil
}

func (c *Channel) addToDeferredPQ(item *pqueue.Item) {
	c.deferredMutex.Lock()
	defer c.deferredMutex.Unlock()
	heap.Push(&c.deferredPQ, item)
}

// Router handles the muxing of incoming Channel messages, either writing
// to the in-memory channel or to the backend
func (c *Channel) router() {
	for {
		select {
		case msg := <-c.incomingMessageChan:
			select {
			case c.memoryMsgChan <- msg:
			default:
				err := WriteMessageToBackend(msg, c)
				if err != nil {
					log.Printf("ERROR: failed to write message to backend - %s", err.Error())
					// TODO: requeue?
					continue
				}
			}
		case <-c.exitChan:
			return
		}
	}
}

// messagePump reads messages from either memory or backend and writes
// to the client output go channel
//
// it is also performs in-flight accounting and initiates the auto-requeue
// goroutine
func (c *Channel) messagePump() {
	var msg *nsq.Message
	var buf []byte
	var err error

	for {
		select {
		case msg = <-c.memoryMsgChan:
		case buf = <-c.backend.ReadChan():
			msg, err = nsq.DecodeMessage(buf)
			if err != nil {
				log.Printf("ERROR: failed to decode message - %s", err.Error())
				continue
			}
		case <-c.exitChan:
			return
		}

		msg.Attempts++

		c.clientMessageChan <- msg
		// the client will call back to mark as in-flight w/ it's info
	}
}

func (c *Channel) deferredWorker() {
	pqWorker(&c.deferredPQ, &c.deferredMutex, func(item *pqueue.Item) {
		msg := item.Value.(*nsq.Message)
		_, err := c.popDeferredMessage(msg.Id)
		if err != nil {
			return
		}
		c.doRequeue(msg)
	})
}

func (c *Channel) inFlightWorker() {
	pqWorker(&c.inFlightPQ, &c.inFlightMutex, func(item *pqueue.Item) {
		client := item.Value.(*inFlightMessage).client
		msg := item.Value.(*inFlightMessage).msg
		_, err := c.popInFlightMessage(msg.Id)
		if err != nil {
			return
		}
		atomic.AddUint64(&c.timeoutCount, 1)
		client.TimedOutMessage()
		c.doRequeue(msg)
	})
}

// generic loop (executed in a goroutine) that periodically wakes up to walk
// the specified (chronological) priority queue and call the callback
//
// if the first element on the queue is not ready (not enough time has elapsed)
// the amount of time to wait before the next iteration is adjusted to optimize
func pqWorker(pq *pqueue.PriorityQueue, mutex *sync.Mutex, callback func(item *pqueue.Item)) {
	waitTime := defaultWorkerWait
	for {
		time.Sleep(waitTime)
		now := time.Now().UnixNano()
		for {
			mutex.Lock()
			item, diff := pq.PeekAndShift(-now)
			mutex.Unlock()

			// never wait a negative amount of time
			// or longer than a second
			duration := time.Duration(diff)
			if duration <= 0 || duration > time.Second {
				waitTime = defaultWorkerWait
			} else {
				waitTime = duration + time.Millisecond
			}

			if item == nil {
				break
			}

			callback(item)
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Knative Authors

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

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

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

package main

import (
	"context"
	"fmt"
	"log"

	cloudevents "github.com/cloudevents/sdk-go"
	"knative.dev/eventing/pkg/kncloudevents"
)

/*
Example Output:

☁  cloudevents.Event:
Validation: valid
Context Attributes,
  SpecVersion: 0.2
  Type: dev.knative.eventing.samples.heartbeat
  Source: https://knative.dev/eventing-contrib/cmd/heartbeats/#local/demo
  ID: 3d2b5a1f-10ca-437b-a374-9c49e43c02fb
  Time: 2019-03-14T21:21:29.366002Z
  ContentType: application/json
  Extensions:
    the: 42
    beats: true
    heart: yes
Transport Context,
  URI: /
  Host: localhost:8080
  Method: POST
Data,
  {
    "id":162,
    "label":""
  }
*/

func display(event cloudevents.Event) {
	fmt.Printf("☁  cloudevents.Event\n%s", event.String())
}

func main() {
	c, err := kncloudevents.NewDefaultClient()
	if err != nil {
		log.Fatal("Failed to create client, ", err)
	}

	log.Fatal(c.StartReceiver(context.Background(), display))
}
<commit_msg>updating to v2 (#1213)<commit_after>/*
Copyright 2019 The Knative Authors

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

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

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

package main

import (
	"context"
	"fmt"
	"log"

	cloudevents "github.com/cloudevents/sdk-go/v2"
)

/*
Example Output:

☁  cloudevents.Event
Validation: valid
Context Attributes,
  specversion: 1.0
  type: dev.knative.eventing.samples.heartbeat
  source: https://knative.dev/eventing-contrib/cmd/heartbeats/#event-test/mypod
  id: 2b72d7bf-c38f-4a98-a433-608fbcdd2596
  time: 2019-10-18T15:23:20.809775386Z
  contenttype: application/json
Extensions,
  beats: true
  heart: yes
  the: 42
Data,
  {
    "id": 2,
    "label": ""
  }
*/

func display(event cloudevents.Event) {
	fmt.Printf("☁  cloudevents.Event\n%s", event.String())
}

func main() {
	c, err := cloudevents.NewDefaultClient()
	if err != nil {
		log.Fatal("Failed to create client, ", err)
	}

	log.Fatal(c.StartReceiver(context.Background(), display))
}
<|endoftext|>


<commit_before>/*
Copyright The Helm Authors.

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

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

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

package main

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

	"sigs.k8s.io/yaml"

	"helm.sh/helm/v3/internal/test/ensure"
	"helm.sh/helm/v3/pkg/helmpath"
	"helm.sh/helm/v3/pkg/helmpath/xdg"
	"helm.sh/helm/v3/pkg/repo"
	"helm.sh/helm/v3/pkg/repo/repotest"
)

func TestRepoAddCmd(t *testing.T) {
	srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
	if err != nil {
		t.Fatal(err)
	}
	defer srv.Stop()

	// A second test server is setup to verify URL changing
	srv2, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
	if err != nil {
		t.Fatal(err)
	}
	defer srv2.Stop()

	tmpdir := ensure.TempDir(t)
	repoFile := filepath.Join(tmpdir, "repositories.yaml")

	tests := []cmdTestCase{
		{
			name:   "add a repository",
			cmd:    fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir),
			golden: "output/repo-add.txt",
		},
		{
			name:   "add repository second time",
			cmd:    fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir),
			golden: "output/repo-add2.txt",
		},
		{
			name:      "add repository different url",
			cmd:       fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv2.URL(), repoFile, tmpdir),
			wantError: true,
		},
		{
			name:   "add repository second time",
			cmd:    fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s --force-update", srv2.URL(), repoFile, tmpdir),
			golden: "output/repo-add.txt",
		},
	}

	runTestCmd(t, tests)
}

func TestRepoAdd(t *testing.T) {
	ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
	if err != nil {
		t.Fatal(err)
	}
	defer ts.Stop()

	rootDir := ensure.TempDir(t)
	repoFile := filepath.Join(rootDir, "repositories.yaml")

	const testRepoName = "test-name"

	o := &repoAddOptions{
		name:               testRepoName,
		url:                ts.URL(),
		forceUpdate:        false,
		deprecatedNoUpdate: true,
		repoFile:           repoFile,
	}
	os.Setenv(xdg.CacheHomeEnvVar, rootDir)

	if err := o.run(ioutil.Discard); err != nil {
		t.Error(err)
	}

	f, err := repo.LoadFile(repoFile)
	if err != nil {
		t.Fatal(err)
	}

	if !f.Has(testRepoName) {
		t.Errorf("%s was not successfully inserted into %s", testRepoName, repoFile)
	}

	idx := filepath.Join(helmpath.CachePath("repository"), helmpath.CacheIndexFile(testRepoName))
	if _, err := os.Stat(idx); os.IsNotExist(err) {
		t.Errorf("Error cache index file was not created for repository %s", testRepoName)
	}
	idx = filepath.Join(helmpath.CachePath("repository"), helmpath.CacheChartsFile(testRepoName))
	if _, err := os.Stat(idx); os.IsNotExist(err) {
		t.Errorf("Error cache charts file was not created for repository %s", testRepoName)
	}

	o.forceUpdate = true

	if err := o.run(ioutil.Discard); err != nil {
		t.Errorf("Repository was not updated: %s", err)
	}

	if err := o.run(ioutil.Discard); err != nil {
		t.Errorf("Duplicate repository name was added")
	}
}

func TestRepoAddConcurrentGoRoutines(t *testing.T) {
	const testName = "test-name"
	repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
	repoAddConcurrent(t, testName, repoFile)
}

func TestRepoAddConcurrentDirNotExist(t *testing.T) {
	const testName = "test-name-2"
	repoFile := filepath.Join(ensure.TempDir(t), "foo", "repositories.yaml")
	repoAddConcurrent(t, testName, repoFile)
}

func TestRepoAddConcurrentNoFileExtension(t *testing.T) {
	const testName = "test-name-3"
	repoFile := filepath.Join(ensure.TempDir(t), "repositories")
	repoAddConcurrent(t, testName, repoFile)
}

func TestRepoAddConcurrentHiddenFile(t *testing.T) {
	const testName = "test-name-4"
	repoFile := filepath.Join(ensure.TempDir(t), ".repositories")
	repoAddConcurrent(t, testName, repoFile)
}

func repoAddConcurrent(t *testing.T, testName, repoFile string) {
	ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
	if err != nil {
		t.Fatal(err)
	}
	defer ts.Stop()

	var wg sync.WaitGroup
	wg.Add(3)
	for i := 0; i < 3; i++ {
		go func(name string) {
			defer wg.Done()
			o := &repoAddOptions{
				name:               name,
				url:                ts.URL(),
				deprecatedNoUpdate: true,
				forceUpdate:        false,
				repoFile:           repoFile,
			}
			if err := o.run(ioutil.Discard); err != nil {
				t.Error(err)
			}
		}(fmt.Sprintf("%s-%d", testName, i))
	}
	wg.Wait()

	b, err := ioutil.ReadFile(repoFile)
	if err != nil {
		t.Error(err)
	}

	var f repo.File
	if err := yaml.Unmarshal(b, &f); err != nil {
		t.Error(err)
	}

	var name string
	for i := 0; i < 3; i++ {
		name = fmt.Sprintf("%s-%d", testName, i)
		if !f.Has(name) {
			t.Errorf("%s was not successfully inserted into %s: %s", name, repoFile, f.Repositories[0])
		}
	}
}

func TestRepoAddFileCompletion(t *testing.T) {
	checkFileCompletion(t, "repo add", false)
	checkFileCompletion(t, "repo add reponame", false)
	checkFileCompletion(t, "repo add reponame https://example.com", false)
}

func TestRepoAddWithPasswordFromStdin(t *testing.T) {
	srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/testserver/*.*")
	defer srv.Stop()

	defer resetEnv()()

	in, err := os.Open("testdata/password")
	if err != nil {
		t.Errorf("unexpected error, got '%v'", err)
	}

	tmpdir := ensure.TempDir(t)
	repoFile := filepath.Join(tmpdir, "repositories.yaml")

	store := storageFixture()

	const testName = "test-name"
	const username = "username"
	cmd := fmt.Sprintf("repo add %s %s --repository-config %s --repository-cache %s --username %s --password-stdin", testName, srv.URL(), repoFile, tmpdir, username)
	var result string
	_, result, err = executeActionCommandStdinC(store, in, cmd)
	if err != nil {
		t.Errorf("unexpected error, got '%v'", err)
	}

	if !strings.Contains(result, fmt.Sprintf("\"%s\" has been added to your repositories", testName)) {
		t.Errorf("Repo was not successfully added. Output: %s", result)
	}
}
<commit_msg>Add extra directories to path used in repo_add_test.go<commit_after>/*
Copyright The Helm Authors.

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

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

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

package main

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

	"sigs.k8s.io/yaml"

	"helm.sh/helm/v3/internal/test/ensure"
	"helm.sh/helm/v3/pkg/helmpath"
	"helm.sh/helm/v3/pkg/helmpath/xdg"
	"helm.sh/helm/v3/pkg/repo"
	"helm.sh/helm/v3/pkg/repo/repotest"
)

func TestRepoAddCmd(t *testing.T) {
	srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
	if err != nil {
		t.Fatal(err)
	}
	defer srv.Stop()

	// A second test server is setup to verify URL changing
	srv2, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
	if err != nil {
		t.Fatal(err)
	}
	defer srv2.Stop()

	tmpdir := filepath.Join(ensure.TempDir(t), "path-component/data")
	err = os.MkdirAll(tmpdir, 0777)
	if err != nil {
		t.Fatal(err)
	}
	repoFile := filepath.Join(tmpdir, "repositories.yaml")

	tests := []cmdTestCase{
		{
			name:   "add a repository",
			cmd:    fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir),
			golden: "output/repo-add.txt",
		},
		{
			name:   "add repository second time",
			cmd:    fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv.URL(), repoFile, tmpdir),
			golden: "output/repo-add2.txt",
		},
		{
			name:      "add repository different url",
			cmd:       fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s", srv2.URL(), repoFile, tmpdir),
			wantError: true,
		},
		{
			name:   "add repository second time",
			cmd:    fmt.Sprintf("repo add test-name %s --repository-config %s --repository-cache %s --force-update", srv2.URL(), repoFile, tmpdir),
			golden: "output/repo-add.txt",
		},
	}

	runTestCmd(t, tests)
}

func TestRepoAdd(t *testing.T) {
	ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
	if err != nil {
		t.Fatal(err)
	}
	defer ts.Stop()

	rootDir := ensure.TempDir(t)
	repoFile := filepath.Join(rootDir, "repositories.yaml")

	const testRepoName = "test-name"

	o := &repoAddOptions{
		name:               testRepoName,
		url:                ts.URL(),
		forceUpdate:        false,
		deprecatedNoUpdate: true,
		repoFile:           repoFile,
	}
	os.Setenv(xdg.CacheHomeEnvVar, rootDir)

	if err := o.run(ioutil.Discard); err != nil {
		t.Error(err)
	}

	f, err := repo.LoadFile(repoFile)
	if err != nil {
		t.Fatal(err)
	}

	if !f.Has(testRepoName) {
		t.Errorf("%s was not successfully inserted into %s", testRepoName, repoFile)
	}

	idx := filepath.Join(helmpath.CachePath("repository"), helmpath.CacheIndexFile(testRepoName))
	if _, err := os.Stat(idx); os.IsNotExist(err) {
		t.Errorf("Error cache index file was not created for repository %s", testRepoName)
	}
	idx = filepath.Join(helmpath.CachePath("repository"), helmpath.CacheChartsFile(testRepoName))
	if _, err := os.Stat(idx); os.IsNotExist(err) {
		t.Errorf("Error cache charts file was not created for repository %s", testRepoName)
	}

	o.forceUpdate = true

	if err := o.run(ioutil.Discard); err != nil {
		t.Errorf("Repository was not updated: %s", err)
	}

	if err := o.run(ioutil.Discard); err != nil {
		t.Errorf("Duplicate repository name was added")
	}
}

func TestRepoAddConcurrentGoRoutines(t *testing.T) {
	const testName = "test-name"
	repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
	repoAddConcurrent(t, testName, repoFile)
}

func TestRepoAddConcurrentDirNotExist(t *testing.T) {
	const testName = "test-name-2"
	repoFile := filepath.Join(ensure.TempDir(t), "foo", "repositories.yaml")
	repoAddConcurrent(t, testName, repoFile)
}

func TestRepoAddConcurrentNoFileExtension(t *testing.T) {
	const testName = "test-name-3"
	repoFile := filepath.Join(ensure.TempDir(t), "repositories")
	repoAddConcurrent(t, testName, repoFile)
}

func TestRepoAddConcurrentHiddenFile(t *testing.T) {
	const testName = "test-name-4"
	repoFile := filepath.Join(ensure.TempDir(t), ".repositories")
	repoAddConcurrent(t, testName, repoFile)
}

func repoAddConcurrent(t *testing.T, testName, repoFile string) {
	ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
	if err != nil {
		t.Fatal(err)
	}
	defer ts.Stop()

	var wg sync.WaitGroup
	wg.Add(3)
	for i := 0; i < 3; i++ {
		go func(name string) {
			defer wg.Done()
			o := &repoAddOptions{
				name:               name,
				url:                ts.URL(),
				deprecatedNoUpdate: true,
				forceUpdate:        false,
				repoFile:           repoFile,
			}
			if err := o.run(ioutil.Discard); err != nil {
				t.Error(err)
			}
		}(fmt.Sprintf("%s-%d", testName, i))
	}
	wg.Wait()

	b, err := ioutil.ReadFile(repoFile)
	if err != nil {
		t.Error(err)
	}

	var f repo.File
	if err := yaml.Unmarshal(b, &f); err != nil {
		t.Error(err)
	}

	var name string
	for i := 0; i < 3; i++ {
		name = fmt.Sprintf("%s-%d", testName, i)
		if !f.Has(name) {
			t.Errorf("%s was not successfully inserted into %s: %s", name, repoFile, f.Repositories[0])
		}
	}
}

func TestRepoAddFileCompletion(t *testing.T) {
	checkFileCompletion(t, "repo add", false)
	checkFileCompletion(t, "repo add reponame", false)
	checkFileCompletion(t, "repo add reponame https://example.com", false)
}

func TestRepoAddWithPasswordFromStdin(t *testing.T) {
	srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/testserver/*.*")
	defer srv.Stop()

	defer resetEnv()()

	in, err := os.Open("testdata/password")
	if err != nil {
		t.Errorf("unexpected error, got '%v'", err)
	}

	tmpdir := ensure.TempDir(t)
	repoFile := filepath.Join(tmpdir, "repositories.yaml")

	store := storageFixture()

	const testName = "test-name"
	const username = "username"
	cmd := fmt.Sprintf("repo add %s %s --repository-config %s --repository-cache %s --username %s --password-stdin", testName, srv.URL(), repoFile, tmpdir, username)
	var result string
	_, result, err = executeActionCommandStdinC(store, in, cmd)
	if err != nil {
		t.Errorf("unexpected error, got '%v'", err)
	}

	if !strings.Contains(result, fmt.Sprintf("\"%s\" has been added to your repositories", testName)) {
		t.Errorf("Repo was not successfully added. Output: %s", result)
	}
}
<|endoftext|>


<commit_before>package random

import (
	"fmt"

	"github.com/MYOB-Technology/pops/lib"
	"github.com/spf13/cobra"
)

var flagRandSecretSize int
var flagRandSecretBase64 bool
var flagRandSecretNewLine string

var randSecretCmd = &cobra.Command{
	Use:   "secret",
	Short: "Create a random secret",
	Long: `Create a random secret. Print to STDOUT.
  If you pipe to a file, we recommend you to chmod the file to 400`,
	Run: func(cmd *cobra.Command, args []string) {
		b := lib.RandomBytes(flagRandSecretSize)
		if flagRandSecretBase64 {
			fmt.Print(lib.EncodeBase64(b))
		} else {
			fmt.Print(string(b))
		}
		fmt.Print(flagRandSecretNewLine)
	},
}

func init() {
	RandCmd.AddCommand(randSecretCmd)
	randSecretCmd.Flags().IntVar(&flagRandSecretSize, "size", 512, "Size of the secret")
	randSecretCmd.Flags().BoolVar(&flagRandSecretBase64, "base64", true, "Whether to encode output in base64")
	randSecretCmd.Flags().StringVar(&flagRandSecretNewLine, "newline", "", "Optionally append a newline to the end of the output")
}
<commit_msg>changed rand secret --newline to a bool<commit_after>package random

import (
	"fmt"

	"github.com/MYOB-Technology/pops/lib"
	"github.com/spf13/cobra"
)

var flagRandSecretSize int
var flagRandSecretBase64 bool
var flagRandSecretNewLine bool

var randSecretCmd = &cobra.Command{
	Use:   "secret",
	Short: "Create a random secret",
	Long: `Create a random secret. Print to STDOUT.
  If you pipe to a file, we recommend you to chmod the file to 400`,
	Run: func(cmd *cobra.Command, args []string) {
		b := lib.RandomBytes(flagRandSecretSize)
		if flagRandSecretBase64 {
			fmt.Print(lib.EncodeBase64(b))
		} else {
			fmt.Print(string(b))
		}
		if flagRandSecretNewLine {
			fmt.Println()
		}
	},
}

func init() {
	RandCmd.AddCommand(randSecretCmd)
	randSecretCmd.Flags().IntVar(&flagRandSecretSize, "size", 512, "Size of the secret")
	randSecretCmd.Flags().BoolVar(&flagRandSecretBase64, "base64", true, "Whether to encode output in base64")
	randSecretCmd.Flags().BoolVar(&flagRandSecretNewLine, "newline", false, "Optionally append a newline to the end of the output")
}
<|endoftext|>


<commit_before>// Package kodi_jsonrpc provides an interface for communicating with a Kodi/XBMC
// server via the raw JSON-RPC socket
//
// Extracted from the kodi-callback-daemon.
//
// Released under the terms of the MIT License (see LICENSE).
package kodi_jsonrpc

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/mitchellh/mapstructure"
)

// Main type for interacting with Kodi
type Connection struct {
	conn          net.Conn
	write         chan interface{}
	Notifications chan Notification
	enc           *json.Encoder
	dec           *json.Decoder
	lock          sync.Mutex
	requestId     uint32
	responses     map[uint32]*chan *rpcResponse

	address string
	timeout time.Duration
}

// RPC Request type
type Request struct {
	Id      *uint32                 `json:"id,omitempty"`
	Method  string                  `json:"method"`
	Params  *map[string]interface{} `json:"params,omitempty"`
	JsonRPC string                  `json:"jsonrpc"`
}

// RPC response error type
type rpcError struct {
	Code    float64                 `json:"code"`
	Message string                  `json:"message"`
	Data    *map[string]interface{} `json:"data"`
}

// RPC Response provides a reader for returning responses
type Response struct {
	channel *chan *rpcResponse
	Pending bool // If Pending is false, Response is unwanted, or been consumed
}

// RPC response type
type rpcResponse struct {
	Id      *float64                `json:"id"`
	JsonRPC string                  `json:"jsonrpc"`
	Method  *string                 `json:"method"`
	Params  *map[string]interface{} `json:"params"`
	Result  *map[string]interface{} `json:"result"`
	Error   *rpcError               `json:"error"`
}

// Notification stores Kodi server->client notifications.
type Notification struct {
	Method string `json:"method" mapstructure:"method"`
	Params struct {
		Data struct {
			Item *struct {
				Type string `json:"type" mapstructure:"type"`
			} `json:"item" mapstructure:"item"` // Optional
		} `json:"data" mapstructure:"data"`
	} `json:"params" mapstructure:"params"`
}

const (
	VERSION = `1.0.0`

	// Minimum Kodi/XBMC API version
	KODI_MIN_VERSION = 6

	LogDebugLevel = log.DebugLevel
	LogInfoLevel  = log.InfoLevel
	LogWarnLevel  = log.WarnLevel
	LogErrorLevel = log.ErrorLevel
	LogFatalLevel = log.FatalLevel
	LogPanicLevel = log.PanicLevel
)

func init() {
	// Initialize logger, default to level Info
	log.SetLevel(LogInfoLevel)
}

// New returns a Connection to the specified address.
// If timeout (seconds) is greater than zero, connection will fail if initial
// version query is not returned within this time.
//
// User must ensure Close() is called on returned Connection when finished with
// it, to avoid leaks.
func New(address string, timeout time.Duration) (conn Connection, err error) {
	conn = Connection{}
	err = conn.init(address, timeout)

	return conn, err
}

// SetLogLevel adjusts the level of logger output, level must be one of:
//
// LogDebugLevel
// LogInfoLevel
// LogWarnLevel
// LogErrorLevel
// LogFatalLevel
// LogPanicLevel
func SetLogLevel(level log.Level) {
	log.SetLevel(level)
}

// Return the result and any errors from the response channel
func (rchan *Response) Read(timeout time.Duration) (result map[string]interface{}, err error) {
	if rchan.Pending != true {
		return result, errors.New(`No pending responses!`)
	}
	if rchan.channel == nil {
		return result, errors.New(`Expected response channel, but got nil!`)
	}

	res := new(rpcResponse)
	if timeout > 0 {
		select {
		case res = <-*rchan.channel:
		case <-time.After(timeout * time.Second):
			err = errors.New(`Timeout waiting on response channel`)
		}
	} else {
		res = <-*rchan.channel
	}
	if err == nil {
		result, err = res.unpack()
	}

	return result, err
}

// Unpack the result and any errors from the Response
func (res *rpcResponse) unpack() (result map[string]interface{}, err error) {
	if res.Error != nil {
		err = errors.New(fmt.Sprintf(
			`Kodi error (%v): %v`, res.Error.Code, res.Error.Message,
		))
	} else if res.Result != nil {
		result = *res.Result
	} else {
		log.WithField(`response`, res).Debug(`Received unknown response type from Kodi`)
	}
	return result, err
}

// init brings up an instance of the Kodi Connection
func (c *Connection) init(address string, timeout time.Duration) (err error) {
	if c.address == `` {
		c.address = address
	}
	if c.timeout == 0 && timeout != 0 {
		c.timeout = timeout
	}

	if err = c.connect(); err != nil {
		return err
	}

	c.write = make(chan interface{}, 4)
	c.Notifications = make(chan Notification, 4)

	c.responses = make(map[uint32]*chan *rpcResponse)

	c.enc = json.NewEncoder(c.conn)
	c.dec = json.NewDecoder(c.conn)

	go c.reader()
	go c.writer()

	rchan := c.Send(Request{Method: `JSONRPC.Version`}, true)

	res, err := rchan.Read(c.timeout)
	if err != nil {
		log.WithField(`error`, err).Error(`Kodi responded`)
		return err
	}
	if version := res[`version`].(map[string]interface{}); version != nil {
		if version[`major`].(float64) < KODI_MIN_VERSION {
			return errors.New(`Kodi version too low, upgrade to Frodo or later`)
		}
	}

	log.Info(`Connected to Kodi`)

	return
}

// Send an RPC Send to the Kodi server.
// Returns a Response, but does not attach a channel for it if want_response is
// false (for fire-and-forget commands that don't return any useful response).
func (c *Connection) Send(req Request, want_response bool) Response {
	req.JsonRPC = `2.0`
	res := Response{}

	if want_response == true {
		c.lock.Lock()
		id := c.requestId
		ch := make(chan *rpcResponse)
		c.responses[id] = &ch
		c.requestId++
		c.lock.Unlock()
		req.Id = &id

		log.WithField(`request`, req).Debug(`Sending Kodi Request (response desired)`)
		c.write <- req
		res.channel = &ch
		res.Pending = true
	} else {
		log.WithField(`request`, req).Debug(`Sending Kodi Request (response undesired)`)
		c.write <- req
		res.Pending = false
	}

	return res
}

// connect establishes a TCP connection
func (c *Connection) connect() (err error) {
	// Reset any existing connections before attempting new Dial
	c.Close()

	c.conn, err = net.Dial(`tcp`, c.address)
	for err != nil {
		log.WithField(`error`, err).Error(`Connecting to Kodi`)
		log.Info(`Attempting reconnect...`)
		time.Sleep(time.Second)
		c.conn, err = net.Dial(`tcp`, c.address)
	}
	err = nil

	return
}

// writer loop processes outbound requests
func (c *Connection) writer() {
	for {
		var req interface{}
		req = <-c.write
		err := c.enc.Encode(req)
		if _, ok := err.(net.Error); ok {
			err = c.init(c.address, c.timeout)
			c.enc.Encode(req)
		} else if err != nil {
			log.WithField(`error`, err).Warn(`Failed encoding request for Kodi`)
			break
		}
	}
}

// reader loop processes inbound responses and notifications
func (c *Connection) reader() {
	for {
		res := new(rpcResponse)
		err := c.dec.Decode(res)
		if _, ok := err.(net.Error); err == io.EOF || ok {
			log.WithField(`error`, err).Error(`Reading from Kodi`)
			log.Error(`If this error persists, make sure you are using the JSON-RPC port, not the HTTP port!`)
			err = c.init(c.address, c.timeout)
		} else if err != nil {
			log.WithField(`error`, err).Error(`Decoding response from Kodi`)
			continue
		}
		if res.Id == nil && res.Method != nil {
			log.WithField(`response.Method`, *res.Method).Debug(`Received notification from Kodi`)
			n := Notification{}
			n.Method = *res.Method
			mapstructure.Decode(res.Params, &n.Params)
			c.Notifications <- n
		} else if res.Id != nil {
			if ch := c.responses[uint32(*res.Id)]; ch != nil {
				if res.Result != nil {
					log.WithField(`response.Result`, *res.Result).Debug(`Received response from Kodi`)
				}
				*ch <- res
			} else {
				log.WithField(`response.Id`, *res.Id).Warn(`Received Kodi response for unknown request`)
				log.WithField(`connection.responses`, c.responses).Debug(`Current response channels`)
			}
		} else {
			if res.Error != nil {
				log.WithField(`response.Error`, *res.Error).Warn(`Received unparseable Kodi response`)
			} else {
				log.WithField(`response`, res).Warn(`Received unparseable Kodi response`)
			}
		}
	}
}

// Close Kodi connection
func (c *Connection) Close() {
	for _, v := range c.responses {
		if v != nil {
			close(*v)
		}
	}
	if c.write != nil {
		close(c.write)
	}
	if c.Notifications != nil {
		close(c.Notifications)
	}
	if c.conn != nil {
		_ = c.conn.Close()
	}
}
<commit_msg>:bug:(reconnect): Less painful reconnect<commit_after>// Package kodi_jsonrpc provides an interface for communicating with a Kodi/XBMC
// server via the raw JSON-RPC socket
//
// Extracted from the kodi-callback-daemon.
//
// Released under the terms of the MIT License (see LICENSE).
package kodi_jsonrpc

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/mitchellh/mapstructure"
)

// Main type for interacting with Kodi
type Connection struct {
	conn             net.Conn
	write            chan interface{}
	Notifications    chan Notification
	enc              *json.Encoder
	dec              *json.Decoder
	responseLock     sync.Mutex
	writeWait        sync.WaitGroup
	notificationWait sync.WaitGroup
	requestId        uint32
	responses        map[uint32]*chan *rpcResponse

	Connected bool
	Closed    bool

	address string
	timeout time.Duration
}

// RPC Request type
type Request struct {
	Id      *uint32                 `json:"id,omitempty"`
	Method  string                  `json:"method"`
	Params  *map[string]interface{} `json:"params,omitempty"`
	JsonRPC string                  `json:"jsonrpc"`
}

// RPC response error type
type rpcError struct {
	Code    float64                 `json:"code"`
	Message string                  `json:"message"`
	Data    *map[string]interface{} `json:"data"`
}

// RPC Response provides a reader for returning responses
type Response struct {
	channel *chan *rpcResponse
	Pending bool // If Pending is false, Response is unwanted, or been consumed
}

// RPC response type
type rpcResponse struct {
	Id      *float64                `json:"id"`
	JsonRPC string                  `json:"jsonrpc"`
	Method  *string                 `json:"method"`
	Params  *map[string]interface{} `json:"params"`
	Result  *map[string]interface{} `json:"result"`
	Error   *rpcError               `json:"error"`
}

// Notification stores Kodi server->client notifications.
type Notification struct {
	Method string `json:"method" mapstructure:"method"`
	Params struct {
		Data struct {
			Item *struct {
				Type string `json:"type" mapstructure:"type"`
			} `json:"item" mapstructure:"item"` // Optional
		} `json:"data" mapstructure:"data"`
	} `json:"params" mapstructure:"params"`
}

const (
	VERSION = `1.0.0`

	// Minimum Kodi/XBMC API version
	KODI_MIN_VERSION = 6

	LogDebugLevel = log.DebugLevel
	LogInfoLevel  = log.InfoLevel
	LogWarnLevel  = log.WarnLevel
	LogErrorLevel = log.ErrorLevel
	LogFatalLevel = log.FatalLevel
	LogPanicLevel = log.PanicLevel
)

func init() {
	// Initialize logger, default to level Info
	log.SetLevel(LogInfoLevel)
}

// New returns a Connection to the specified address.
// If timeout (seconds) is greater than zero, connection will fail if initial
// version query is not returned within this time.
//
// User must ensure Close() is called on returned Connection when finished with
// it, to avoid leaks.
func New(address string, timeout time.Duration) (conn Connection, err error) {
	conn = Connection{}
	err = conn.init(address, timeout)

	return conn, err
}

// SetLogLevel adjusts the level of logger output, level must be one of:
//
// LogDebugLevel
// LogInfoLevel
// LogWarnLevel
// LogErrorLevel
// LogFatalLevel
// LogPanicLevel
func SetLogLevel(level log.Level) {
	log.SetLevel(level)
}

// Return the result and any errors from the response channel
func (rchan *Response) Read(timeout time.Duration) (result map[string]interface{}, err error) {
	if rchan.Pending != true {
		return result, errors.New(`No pending responses!`)
	}
	if rchan.channel == nil {
		return result, errors.New(`Expected response channel, but got nil!`)
	}

	res := new(rpcResponse)
	if timeout > 0 {
		select {
		case res = <-*rchan.channel:
		case <-time.After(timeout * time.Second):
			err = errors.New(`Timeout waiting on response channel`)
		}
	} else {
		res = <-*rchan.channel
	}
	if err == nil {
		result, err = res.unpack()
	}
	close(*rchan.channel)

	return result, err
}

// Unpack the result and any errors from the Response
func (res *rpcResponse) unpack() (result map[string]interface{}, err error) {
	if res.Error != nil {
		err = errors.New(fmt.Sprintf(
			`Kodi error (%v): %v`, res.Error.Code, res.Error.Message,
		))
	} else if res.Result != nil {
		result = *res.Result
	} else {
		log.WithField(`response`, res).Debug(`Received unknown response type from Kodi`)
	}
	return result, err
}

// init brings up an instance of the Kodi Connection
func (c *Connection) init(address string, timeout time.Duration) (err error) {

	if c.address == `` {
		c.address = address
	}
	if c.timeout == 0 && timeout != 0 {
		c.timeout = timeout
	}

	if err = c.connect(); err != nil {
		return err
	}

	c.write = make(chan interface{}, 16)
	c.Notifications = make(chan Notification, 16)

	c.responses = make(map[uint32]*chan *rpcResponse)

	go c.reader()
	go c.writer()

	rchan := c.Send(Request{Method: `JSONRPC.Version`}, true)

	res, err := rchan.Read(c.timeout)
	if err != nil {
		log.WithField(`error`, err).Error(`Kodi responded`)
		return err
	}
	if version := res[`version`].(map[string]interface{}); version != nil {
		if version[`major`].(float64) < KODI_MIN_VERSION {
			return errors.New(`Kodi version too low, upgrade to Frodo or later`)
		}
	}

	return
}

// Send an RPC Send to the Kodi server.
// Returns a Response, but does not attach a channel for it if want_response is
// false (for fire-and-forget commands that don't return any useful response).
func (c *Connection) Send(req Request, want_response bool) Response {
	req.JsonRPC = `2.0`
	res := Response{}

	c.writeWait.Add(1)
	if want_response == true {
		c.responseLock.Lock()
		id := c.requestId
		ch := make(chan *rpcResponse)
		c.responses[id] = &ch
		c.requestId++
		c.responseLock.Unlock()
		req.Id = &id

		log.WithField(`request`, req).Debug(`Sending Kodi Request (response desired)`)
		c.write <- req
		res.channel = &ch
		res.Pending = true
	} else {
		log.WithField(`request`, req).Debug(`Sending Kodi Request (response undesired)`)
		c.write <- req
		res.Pending = false
	}
	c.writeWait.Done()

	return res
}

// set whether we're connected or not
func (c *Connection) connected(status bool) {
}

// connect establishes a TCP connection
func (c *Connection) connect() (err error) {
	c.connected(false)
	defer c.connected(true)

	c.conn, err = net.Dial(`tcp`, c.address)
	for err != nil {
		log.WithField(`error`, err).Error(`Connecting to Kodi`)
		log.Info(`Attempting reconnect...`)
		time.Sleep(time.Second)
		c.conn, err = net.Dial(`tcp`, c.address)
	}
	err = nil

	c.enc = json.NewEncoder(c.conn)
	c.dec = json.NewDecoder(c.conn)

	log.Info(`Connected to Kodi`)

	return
}

// writer loop processes outbound requests
func (c *Connection) writer() {
	for {
		var req interface{}
		req = <-c.write
		err := c.enc.Encode(req)
		if _, ok := err.(net.Error); ok {
			err = c.connect()
			c.enc.Encode(req)
		} else if err != nil {
			log.WithField(`error`, err).Warn(`Failed encoding request for Kodi`)
			err = c.connect()
			c.enc.Encode(req)
		}
	}
}

// reader loop processes inbound responses and notifications
func (c *Connection) reader() {
	for {
		res := new(rpcResponse)
		err := c.dec.Decode(res)
		if _, ok := err.(net.Error); err == io.EOF || ok {
			log.WithField(`error`, err).Error(`Reading from Kodi`)
			log.Error(`If this error persists, make sure you are using the JSON-RPC port, not the HTTP port!`)
			err = c.connect()
		} else if err != nil {
			log.WithField(`error`, err).Error(`Decoding response from Kodi`)
			continue
		}
		if res.Id == nil && res.Method != nil {
			c.notificationWait.Add(1)
			log.WithField(`response.Method`, *res.Method).Debug(`Received notification from Kodi`)
			n := Notification{}
			n.Method = *res.Method
			mapstructure.Decode(res.Params, &n.Params)
			c.Notifications <- n
			c.notificationWait.Done()
		} else if res.Id != nil {
			if ch := c.responses[uint32(*res.Id)]; ch != nil {
				if res.Result != nil {
					log.WithField(`response.Result`, *res.Result).Debug(`Received response from Kodi`)
				}
				*ch <- res
			} else {
				log.WithField(`response.Id`, *res.Id).Warn(`Received Kodi response for unknown request`)
				log.WithField(`connection.responses`, c.responses).Debug(`Current response channels`)
			}
		} else {
			if res.Error != nil {
				log.WithField(`response.Error`, *res.Error).Warn(`Received unparseable Kodi response`)
			} else {
				log.WithField(`response`, res).Warn(`Received unparseable Kodi response`)
			}
		}
	}
}

// Close Kodi connection
func (c *Connection) Close() {
	if c.Closed {
		return
	}
	c.Closed = true

	if c.write != nil {
		c.writeWait.Wait()
		close(c.write)
	}
	if c.Notifications != nil {
		c.notificationWait.Wait()
		close(c.Notifications)
	}
	if c.conn != nil {
		_ = c.conn.Close()
	}

	log.Info(`Disconnected from Kodi`)
}
<|endoftext|>


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

package main

import (
	"fmt"
	"os"
	"os/user"
	"regexp"
	"strings"
	"syscall"
	"time"
)

// From Linux header: /include/uapi/linux/kdev_t.h
const (
	minorBits = 8
	minorMask = (1 << minorBits) - 1
)

// Matches characters which would interfere with ls's formatting.
var unprintableRe = regexp.MustCompile("[[:cntrl:]\n]")

// Since `os.FileInfo` is an interface, it is difficult to tweak some of its
// internal values. For example, replacing the starting directory with a dot.
// `extractImportantParts` populates our own struct which we can modify at will
// before printing.
type fileInfo struct {
	name         string
	mode         os.FileMode
	major, minor uint64
	uid, gid     uint32
	size         int64
	modTime      time.Time
}

func extractImportantParts(fi os.FileInfo) fileInfo {
	s := fi.Sys().(*syscall.Stat_t)
	return fileInfo{
		name:    fi.Name(),
		mode:    fi.Mode(),
		major:   s.Rdev & minorMask,
		minor:   s.Rdev >> minorBits,
		uid:     s.Uid,
		gid:     s.Gid,
		size:    fi.Size(),
		modTime: fi.ModTime(),
	}
}

// Without this cache, `ls -l` is orders of magnitude slower.
var (
	uidCache = map[uint32]string{}
	gidCache = map[uint32]string{}
)

// Convert uid to username, or return uid on error.
func lookupUserName(id uint32) string {
	if s, ok := uidCache[id]; ok {
		return s
	}
	s := fmt.Sprint(id)
	if u, err := user.LookupId(s); err == nil {
		s = u.Username
	}
	uidCache[id] = s
	return s
}

// Convert gid to group name, or return gid on error.
func lookupGroupName(id uint32) string {
	if s, ok := gidCache[id]; ok {
		return s
	}
	s := fmt.Sprint(id)
	if g, err := user.LookupGroupId(s); err == nil {
		s = g.Name
	}
	gidCache[id] = s
	return s
}

// The default stringer. Return only the filename. Unprintable characters are
// replaced with '?'.
func (fi fileInfo) String() string {
	return unprintableRe.ReplaceAllLiteralString(fi.name, "?")
}

// Two alternative stringers
type quotedStringer struct {
	fileInfo
}
type longStringer struct {
	fileInfo
	comp fmt.Stringer // decorator pattern
}

// Return the name surrounded by quotes with escaped control characters.
func (fi quotedStringer) String() string {
	return fmt.Sprintf("%#v", fi.name)
}

// The long and quoted stringers can be combined like so:
//     longStringer{fi, quotedStringer{fi}}
func (fi longStringer) String() string {
	// Golang's FileMode.String() is almost sufficient, except we would
	// rather use b and c for devices.
	replacer := strings.NewReplacer("Dc", "c", "D", "b")

	// Ex: crw-rw-rw-  root  root  1, 3  Feb 6 09:31  null
	pattern := "%[1]s\t%[2]s\t%[3]s\t%[4]d, %[5]d\t%[7]v\t%[8]s"
	if fi.major == 0 && fi.minor == 0 {
		// Ex: -rw-rw----  myuser  myuser  1256  Feb 6 09:31  recipes.txt
		pattern = "%[1]s\t%[2]s\t%[3]s\t%[6]d\t%[7]v\t%[8]s"
	}

	return fmt.Sprintf(pattern,
		replacer.Replace(fi.mode.String()),
		lookupUserName(fi.uid),
		lookupGroupName(fi.gid),
		fi.minor,
		fi.major,
		fi.size,
		fi.modTime.Format("Jan _2 15:04"),
		fi.comp.String())
}
<commit_msg>The major and minor where accidently swapped -- twice!<commit_after>// Copyright 2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"os"
	"os/user"
	"regexp"
	"strings"
	"syscall"
	"time"
)

// From Linux header: /include/uapi/linux/kdev_t.h
const (
	minorBits = 8
	minorMask = (1 << minorBits) - 1
)

// Matches characters which would interfere with ls's formatting.
var unprintableRe = regexp.MustCompile("[[:cntrl:]\n]")

// Since `os.FileInfo` is an interface, it is difficult to tweak some of its
// internal values. For example, replacing the starting directory with a dot.
// `extractImportantParts` populates our own struct which we can modify at will
// before printing.
type fileInfo struct {
	name         string
	mode         os.FileMode
	major, minor uint64
	uid, gid     uint32
	size         int64
	modTime      time.Time
}

func extractImportantParts(fi os.FileInfo) fileInfo {
	s := fi.Sys().(*syscall.Stat_t)
	return fileInfo{
		name:    fi.Name(),
		mode:    fi.Mode(),
		major:   s.Rdev >> minorBits,
		minor:   s.Rdev & minorMask,
		uid:     s.Uid,
		gid:     s.Gid,
		size:    fi.Size(),
		modTime: fi.ModTime(),
	}
}

// Without this cache, `ls -l` is orders of magnitude slower.
var (
	uidCache = map[uint32]string{}
	gidCache = map[uint32]string{}
)

// Convert uid to username, or return uid on error.
func lookupUserName(id uint32) string {
	if s, ok := uidCache[id]; ok {
		return s
	}
	s := fmt.Sprint(id)
	if u, err := user.LookupId(s); err == nil {
		s = u.Username
	}
	uidCache[id] = s
	return s
}

// Convert gid to group name, or return gid on error.
func lookupGroupName(id uint32) string {
	if s, ok := gidCache[id]; ok {
		return s
	}
	s := fmt.Sprint(id)
	if g, err := user.LookupGroupId(s); err == nil {
		s = g.Name
	}
	gidCache[id] = s
	return s
}

// The default stringer. Return only the filename. Unprintable characters are
// replaced with '?'.
func (fi fileInfo) String() string {
	return unprintableRe.ReplaceAllLiteralString(fi.name, "?")
}

// Two alternative stringers
type quotedStringer struct {
	fileInfo
}
type longStringer struct {
	fileInfo
	comp fmt.Stringer // decorator pattern
}

// Return the name surrounded by quotes with escaped control characters.
func (fi quotedStringer) String() string {
	return fmt.Sprintf("%#v", fi.name)
}

// The long and quoted stringers can be combined like so:
//     longStringer{fi, quotedStringer{fi}}
func (fi longStringer) String() string {
	// Golang's FileMode.String() is almost sufficient, except we would
	// rather use b and c for devices.
	replacer := strings.NewReplacer("Dc", "c", "D", "b")

	// Ex: crw-rw-rw-  root  root  1, 3  Feb 6 09:31  null
	pattern := "%[1]s\t%[2]s\t%[3]s\t%[4]d, %[5]d\t%[7]v\t%[8]s"
	if fi.major == 0 && fi.minor == 0 {
		// Ex: -rw-rw----  myuser  myuser  1256  Feb 6 09:31  recipes.txt
		pattern = "%[1]s\t%[2]s\t%[3]s\t%[6]d\t%[7]v\t%[8]s"
	}

	return fmt.Sprintf(pattern,
		replacer.Replace(fi.mode.String()),
		lookupUserName(fi.uid),
		lookupGroupName(fi.gid),
		fi.major,
		fi.minor,
		fi.size,
		fi.modTime.Format("Jan _2 15:04"),
		fi.comp.String())
}
<|endoftext|>


<commit_before>package analysis

import (
	"go/ast"
	"go/token"

	"github.com/gopherjs/gopherjs/compiler/util"

	"golang.org/x/tools/go/types"
)

type Info struct {
	*types.Info
	Pkg           *types.Package
	IsBlocking    func(*types.Func) bool
	FuncDeclInfos map[*types.Func]*FuncInfo
	FuncLitInfos  map[*ast.FuncLit]*FuncInfo
	InitFuncInfo  *FuncInfo
	comments      ast.CommentMap
}

type FuncInfo struct {
	HasDefer     bool
	Flattened    map[ast.Node]bool
	Blocking     map[ast.Node]bool
	GotoLabel    map[*types.Label]bool
	LocalCalls   map[*types.Func][][]ast.Node
	p            *Info
	analyzeStack []ast.Node
}

func NewFuncInfo(p *Info) *FuncInfo {
	return &FuncInfo{
		p:          p,
		Flattened:  make(map[ast.Node]bool),
		Blocking:   make(map[ast.Node]bool),
		GotoLabel:  make(map[*types.Label]bool),
		LocalCalls: make(map[*types.Func][][]ast.Node),
	}
}

func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info {
	info := &Info{
		Info:          typesInfo,
		Pkg:           typesPkg,
		comments:      make(ast.CommentMap),
		IsBlocking:    isBlocking,
		FuncDeclInfos: make(map[*types.Func]*FuncInfo),
		FuncLitInfos:  make(map[*ast.FuncLit]*FuncInfo),
	}

	info.InitFuncInfo = NewFuncInfo(info)
	info.FuncLitInfos[nil] = info.InitFuncInfo
	for _, file := range files {
		for k, v := range ast.NewCommentMap(fileSet, file, file.Comments) {
			info.comments[k] = v
		}
		ast.Walk(info.InitFuncInfo, file)
	}
	for {
		done := true
		for _, funcInfo := range info.FuncDeclInfos {
			for obj, calls := range funcInfo.LocalCalls {
				if len(info.FuncDeclInfos[obj].Blocking) != 0 {
					for _, call := range calls {
						funcInfo.markBlocking(call)
					}
					delete(funcInfo.LocalCalls, obj)
					done = false
				}
			}
		}
		if done {
			break
		}
	}
	for _, funcInfo := range info.FuncLitInfos {
		for obj, calls := range funcInfo.LocalCalls {
			if len(info.FuncDeclInfos[obj].Blocking) != 0 {
				for _, call := range calls {
					funcInfo.markBlocking(call)
				}
			}
		}
	}

	return info
}

func (c *FuncInfo) Visit(node ast.Node) ast.Visitor {
	if node == nil {
		if len(c.analyzeStack) != 0 {
			c.analyzeStack = c.analyzeStack[:len(c.analyzeStack)-1]
		}
		return nil
	}
	c.analyzeStack = append(c.analyzeStack, node)

	switch n := node.(type) {
	case *ast.FuncDecl:
		newInfo := NewFuncInfo(c.p)
		c.p.FuncDeclInfos[c.p.Defs[n.Name].(*types.Func)] = newInfo
		return newInfo
	case *ast.FuncLit:
		newInfo := NewFuncInfo(c.p)
		c.p.FuncLitInfos[n] = newInfo
		return newInfo
	case *ast.BranchStmt:
		if n.Tok == token.GOTO {
			for _, n2 := range c.analyzeStack {
				c.Flattened[n2] = true
			}
			c.GotoLabel[c.p.Uses[n.Label].(*types.Label)] = true
		}
	case *ast.CallExpr:
		lookForComment := func() {
			for i := len(c.analyzeStack) - 1; i >= 0; i-- {
				n2 := c.analyzeStack[i]
				for _, group := range c.p.comments[n2] {
					for _, comment := range group.List {
						if comment.Text == "//gopherjs:blocking" {
							c.markBlocking(c.analyzeStack)
							return
						}
					}
				}
				if _, ok := n2.(ast.Stmt); ok {
					break
				}
			}
		}
		callTo := func(obj types.Object) {
			switch o := obj.(type) {
			case *types.Func:
				if recv := o.Type().(*types.Signature).Recv(); recv != nil {
					if _, ok := recv.Type().Underlying().(*types.Interface); ok {
						lookForComment()
						return
					}
				}
				if o.Pkg() != c.p.Pkg {
					if c.p.IsBlocking(o) {
						c.markBlocking(c.analyzeStack)
					}
					return
				}
				stack := make([]ast.Node, len(c.analyzeStack))
				copy(stack, c.analyzeStack)
				c.LocalCalls[o] = append(c.LocalCalls[o], stack)
			case *types.Var:
				lookForComment()
			}
		}
		switch f := util.RemoveParens(n.Fun).(type) {
		case *ast.Ident:
			callTo(c.p.Uses[f])
		case *ast.SelectorExpr:
			if sel := c.p.Selections[f]; sel != nil && util.IsJsObject(sel.Recv()) {
				break
			}
			callTo(c.p.Uses[f.Sel])
		default:
			if !util.IsTypeExpr(f, c.p.Info) {
				lookForComment()
			}
		}
	case *ast.SendStmt:
		c.markBlocking(c.analyzeStack)
	case *ast.UnaryExpr:
		if n.Op == token.ARROW {
			c.markBlocking(c.analyzeStack)
		}
	case *ast.RangeStmt:
		if _, ok := c.p.Types[n.X].Type.Underlying().(*types.Chan); ok {
			c.markBlocking(c.analyzeStack)
		}
	case *ast.SelectStmt:
		for _, s := range n.Body.List {
			if s.(*ast.CommClause).Comm == nil { // default clause
				return c
			}
		}
		c.markBlocking(c.analyzeStack)
	case *ast.CommClause:
		for _, s := range n.Body {
			ast.Walk(c, s)
		}
		return nil
	case *ast.GoStmt:
		ast.Walk(c, n.Call.Fun)
		for _, arg := range n.Call.Args {
			ast.Walk(c, arg)
		}
		return nil
	case *ast.DeferStmt:
		c.HasDefer = true
		if funcLit, ok := n.Call.Fun.(*ast.FuncLit); ok {
			ast.Walk(c, funcLit.Body)
		}
	}
	return c
}

func (c *FuncInfo) markBlocking(stack []ast.Node) {
	for _, n := range stack {
		c.Blocking[n] = true
		c.Flattened[n] = true
	}
}
<commit_msg>fixed blocking for loop post statement<commit_after>package analysis

import (
	"go/ast"
	"go/token"

	"github.com/gopherjs/gopherjs/compiler/util"

	"golang.org/x/tools/go/types"
)

type continueStmt struct {
	forStmt      *ast.ForStmt
	analyzeStack []ast.Node
}

type Info struct {
	*types.Info
	Pkg           *types.Package
	IsBlocking    func(*types.Func) bool
	FuncDeclInfos map[*types.Func]*FuncInfo
	FuncLitInfos  map[*ast.FuncLit]*FuncInfo
	InitFuncInfo  *FuncInfo
	allInfos      []*FuncInfo
	comments      ast.CommentMap
}

type FuncInfo struct {
	HasDefer      bool
	Flattened     map[ast.Node]bool
	Blocking      map[ast.Node]bool
	GotoLabel     map[*types.Label]bool
	LocalCalls    map[*types.Func][][]ast.Node
	ContinueStmts []continueStmt
	p             *Info
	analyzeStack  []ast.Node
}

func (info *Info) newFuncInfo() *FuncInfo {
	funcInfo := &FuncInfo{
		p:          info,
		Flattened:  make(map[ast.Node]bool),
		Blocking:   make(map[ast.Node]bool),
		GotoLabel:  make(map[*types.Label]bool),
		LocalCalls: make(map[*types.Func][][]ast.Node),
	}
	info.allInfos = append(info.allInfos, funcInfo)
	return funcInfo
}

func AnalyzePkg(files []*ast.File, fileSet *token.FileSet, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(*types.Func) bool) *Info {
	info := &Info{
		Info:          typesInfo,
		Pkg:           typesPkg,
		comments:      make(ast.CommentMap),
		IsBlocking:    isBlocking,
		FuncDeclInfos: make(map[*types.Func]*FuncInfo),
		FuncLitInfos:  make(map[*ast.FuncLit]*FuncInfo),
	}
	info.InitFuncInfo = info.newFuncInfo()

	for _, file := range files {
		for k, v := range ast.NewCommentMap(fileSet, file, file.Comments) {
			info.comments[k] = v
		}
		ast.Walk(info.InitFuncInfo, file)
	}

	for {
		done := true
		for _, funcInfo := range info.allInfos {
			for obj, calls := range funcInfo.LocalCalls {
				if len(info.FuncDeclInfos[obj].Blocking) != 0 {
					for _, call := range calls {
						funcInfo.markBlocking(call)
					}
					delete(funcInfo.LocalCalls, obj)
					done = false
				}
			}
		}
		if done {
			break
		}
	}

	for _, funcInfo := range info.allInfos {
		for _, continueStmt := range funcInfo.ContinueStmts {
			if funcInfo.Blocking[continueStmt.forStmt.Post] {
				funcInfo.markBlocking(continueStmt.analyzeStack)
			}
		}
	}

	return info
}

func (c *FuncInfo) Visit(node ast.Node) ast.Visitor {
	if node == nil {
		if len(c.analyzeStack) != 0 {
			c.analyzeStack = c.analyzeStack[:len(c.analyzeStack)-1]
		}
		return nil
	}
	c.analyzeStack = append(c.analyzeStack, node)

	switch n := node.(type) {
	case *ast.FuncDecl:
		newInfo := c.p.newFuncInfo()
		c.p.FuncDeclInfos[c.p.Defs[n.Name].(*types.Func)] = newInfo
		return newInfo
	case *ast.FuncLit:
		newInfo := c.p.newFuncInfo()
		c.p.FuncLitInfos[n] = newInfo
		return newInfo
	case *ast.BranchStmt:
		switch n.Tok {
		case token.GOTO:
			for _, n2 := range c.analyzeStack {
				c.Flattened[n2] = true
			}
			c.GotoLabel[c.p.Uses[n.Label].(*types.Label)] = true
		case token.CONTINUE:
			if n.Label != nil {
				label := c.p.Uses[n.Label].(*types.Label)
				for i := len(c.analyzeStack) - 1; i >= 0; i-- {
					if labelStmt, ok := c.analyzeStack[i].(*ast.LabeledStmt); ok && c.p.Defs[labelStmt.Label] == label {
						if _, ok := labelStmt.Stmt.(*ast.RangeStmt); ok {
							return nil
						}
						stack := make([]ast.Node, len(c.analyzeStack))
						copy(stack, c.analyzeStack)
						c.ContinueStmts = append(c.ContinueStmts, continueStmt{labelStmt.Stmt.(*ast.ForStmt), stack})
						return nil
					}
				}
				return nil
			}
			for i := len(c.analyzeStack) - 1; i >= 0; i-- {
				if _, ok := c.analyzeStack[i].(*ast.RangeStmt); ok {
					return nil
				}
				if forStmt, ok := c.analyzeStack[i].(*ast.ForStmt); ok {
					stack := make([]ast.Node, len(c.analyzeStack))
					copy(stack, c.analyzeStack)
					c.ContinueStmts = append(c.ContinueStmts, continueStmt{forStmt, stack})
					return nil
				}
			}
		}
	case *ast.CallExpr:
		lookForComment := func() {
			for i := len(c.analyzeStack) - 1; i >= 0; i-- {
				n2 := c.analyzeStack[i]
				for _, group := range c.p.comments[n2] {
					for _, comment := range group.List {
						if comment.Text == "//gopherjs:blocking" {
							c.markBlocking(c.analyzeStack)
							return
						}
					}
				}
				if _, ok := n2.(ast.Stmt); ok {
					break
				}
			}
		}
		callTo := func(obj types.Object) {
			switch o := obj.(type) {
			case *types.Func:
				if recv := o.Type().(*types.Signature).Recv(); recv != nil {
					if _, ok := recv.Type().Underlying().(*types.Interface); ok {
						lookForComment()
						return
					}
				}
				if o.Pkg() != c.p.Pkg {
					if c.p.IsBlocking(o) {
						c.markBlocking(c.analyzeStack)
					}
					return
				}
				stack := make([]ast.Node, len(c.analyzeStack))
				copy(stack, c.analyzeStack)
				c.LocalCalls[o] = append(c.LocalCalls[o], stack)
			case *types.Var:
				lookForComment()
			}
		}
		switch f := util.RemoveParens(n.Fun).(type) {
		case *ast.Ident:
			callTo(c.p.Uses[f])
		case *ast.SelectorExpr:
			if sel := c.p.Selections[f]; sel != nil && util.IsJsObject(sel.Recv()) {
				break
			}
			callTo(c.p.Uses[f.Sel])
		default:
			if !util.IsTypeExpr(f, c.p.Info) {
				lookForComment()
			}
		}
	case *ast.SendStmt:
		c.markBlocking(c.analyzeStack)
	case *ast.UnaryExpr:
		if n.Op == token.ARROW {
			c.markBlocking(c.analyzeStack)
		}
	case *ast.RangeStmt:
		if _, ok := c.p.Types[n.X].Type.Underlying().(*types.Chan); ok {
			c.markBlocking(c.analyzeStack)
		}
	case *ast.SelectStmt:
		for _, s := range n.Body.List {
			if s.(*ast.CommClause).Comm == nil { // default clause
				return c
			}
		}
		c.markBlocking(c.analyzeStack)
	case *ast.CommClause:
		for _, s := range n.Body {
			ast.Walk(c, s)
		}
		return nil
	case *ast.GoStmt:
		ast.Walk(c, n.Call.Fun)
		for _, arg := range n.Call.Args {
			ast.Walk(c, arg)
		}
		return nil
	case *ast.DeferStmt:
		c.HasDefer = true
		if funcLit, ok := n.Call.Fun.(*ast.FuncLit); ok {
			ast.Walk(c, funcLit.Body)
		}
	}
	return c
}

func (c *FuncInfo) markBlocking(stack []ast.Node) {
	for _, n := range stack {
		c.Blocking[n] = true
		c.Flattened[n] = true
	}
}
<|endoftext|>


<commit_before>//Copyright 2014 Rana Ian. All rights reserved.
//Use of this source code is governed by The MIT License
//found in the accompanying LICENSE file.

package ora_test

import (
	"testing"

	"gopkg.in/rana/ora.v3"
)

//// bytes
//longRaw     oracleColumnType = "long raw not null"
//longRawNull oracleColumnType = "long raw null"
//raw2000     oracleColumnType = "raw(2000) not null"
//raw2000Null oracleColumnType = "raw(2000) null"
//blob        oracleColumnType = "blob not null"
//blobNull    oracleColumnType = "blob null"

//////////////////////////////////////////////////////////////////////////////////
//// longRaw
//////////////////////////////////////////////////////////////////////////////////
func TestBindDefine_bytes_longRaw_session(t *testing.T) {
	t.Parallel()
	//enableLogging(t)
	testBindDefine(gen_bytes(9), longRaw, t, nil, ora.Bin)
}

func TestBindDefine_OraBytes_longRaw_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_OraBytes(9, false), longRaw, t, nil, ora.OraBin)
}

func TestBindSlice_bytes_longRaw_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_bytesSlice(9), longRaw, t, nil)
}

func TestBindSlice_OraBytes_longRaw_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_OraBytesSlice(9, false), longRaw, t, nil)
}

func TestMultiDefine_longRaw_session(t *testing.T) {
	t.Parallel()
	//enableLogging(t)
	testMultiDefine(gen_bytes(9), longRaw, t)
}

//// Do not test workload of multiple Oracle LONG RAW types within the same table because
//// ORA-01754: a table may contain only one column of type LONG
//func TestWorkload_longRaw_session(t *testing.T) {
//	testWorkload(testWorkloadColumnCount, t, longRaw)
//}

////////////////////////////////////////////////////////////////////////////////
// longRawNull
////////////////////////////////////////////////////////////////////////////////
func TestBindDefine_bytes_longRawNull_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_bytes(9), longRawNull, t, nil, ora.Bin)
}

func TestBindDefine_OraBytes_longRawNull_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_OraBytes(9, true), longRawNull, t, nil, ora.OraBin)
}

func TestBindSlice_bytes_longRawNull_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_bytesSlice(9), longRawNull, t, nil)
}

func TestBindSlice_OraBytes_longRawNull_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_OraBytesSlice(9, true), longRawNull, t, nil)
}

func TestMultiDefine_longRawNull_session(t *testing.T) {
	t.Parallel()
	testMultiDefine(gen_bytes(9), longRawNull, t)
}

//// Do not test workload of multiple Oracle LONG RAW types within the same table because
//// ORA-01754: a table may contain only one column of type LONG
//func TestWorkload_longRawNull_session(t *testing.T) {
//	testWorkload(testWorkloadColumnCount, t, longRawNull)
//}

func TestBindDefine_longRawNull_nil_session(t *testing.T) {
	t.Parallel()
	testBindDefine(nil, longRawNull, t, nil)
}

////////////////////////////////////////////////////////////////////////////////
// raw2000
////////////////////////////////////////////////////////////////////////////////
func TestBindDefine_bytes_raw2000_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_bytes(2000), raw2000, t, nil, ora.Bin)
}

func TestBindDefine_OraBytes_raw2000_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_OraBytes(2000, false), raw2000, t, nil, ora.OraBin)
}

func TestBindSlice_bytes_raw2000_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_bytesSlice(2000), raw2000, t, nil)
}

func TestBindSlice_OraBytes_raw2000_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_OraBytesSlice(2000, false), raw2000, t, nil)
}

func TestMultiDefine_raw2000_session(t *testing.T) {
	t.Parallel()
	testMultiDefine(gen_bytes(2000), raw2000, t)
}

func TestWorkload_raw2000_session(t *testing.T) {
	t.Parallel()
	testWorkload(raw2000, t)
}

////////////////////////////////////////////////////////////////////////////////
// raw2000Null
////////////////////////////////////////////////////////////////////////////////
func TestBindDefine_bytes_raw2000Null_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_bytes(2000), raw2000Null, t, nil, ora.Bin)
}

func TestBindDefine_OraBytes_raw2000Null_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_OraBytes(2000, true), raw2000Null, t, nil, ora.OraBin)
}

func TestBindSlice_bytes_raw2000Null_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_bytesSlice(2000), raw2000Null, t, nil)
}

func TestBindSlice_OraBytes_raw2000Null_session(t *testing.T) {
	t.Parallel()
	testBindDefine(gen_OraBytesSlice(2000, true), raw2000Null, t, nil)
}

func TestMultiDefine_raw2000Null_session(t *testing.T) {
	t.Parallel()
	testMultiDefine(gen_bytes(2000), raw2000Null, t)
}

func TestWorkload_raw2000Null_session(t *testing.T) {
	t.Parallel()
	testWorkload(raw2000Null, t)
}

func TestBindDefine_raw2000Null_nil_session(t *testing.T) {
	t.Parallel()
	testBindDefine(nil, raw2000Null, t, nil)
}

////////////////////////////////////////////////////////////////////////////////
// blob
////////////////////////////////////////////////////////////////////////////////
func TestBindDefine_bytes_blob_session(t *testing.T) {
	testBindDefine(gen_bytes(9), blob, t, nil, ora.Bin)
}

func TestBindDefine_OraBytes_blob_session(t *testing.T) {
	testBindDefine(gen_OraBytes(9, false), blob, t, nil, ora.OraBin)
}

func TestBindDefine_Lob_blob_session(t *testing.T) {
	//enableLogging(t)
	testBindDefine(gen_OraBytesLob(9, false), blob, t, nil, ora.OraBin)
}

func TestBindSlice_bytes_blob_session(t *testing.T) {
	ora.Cfg().Log.Rset.BeginRow = true
	ora.Cfg().Log.Rset.EndRow = true
	ora.Cfg().Log.Rset.Next = true
	//enableLogging(t)
	//ora.Cfg().Log.Logger = lg.Log
	testBindDefine(gen_bytesSlice(9), blob, t, nil)
}

func TestBindSlice_OraBytes_blob_session(t *testing.T) {
	testBindDefine(gen_OraBytesSlice(9, false), blob, t, nil)
}

func TestMultiDefine_blob_session(t *testing.T) {
	//enableLogging(t)
	testMultiDefine(gen_bytes(9), blob, t)
}

func TestWorkload_blob_session(t *testing.T) {
	testWorkload(blob, t)
}

func TestBindDefine_bytes_blob_bufferSize_session(t *testing.T) {
	sc := ora.NewStmtCfg()
	testBindDefine(gen_bytes(sc.LobBufferSize()), blob, t, nil, ora.Bin)
}

func TestBindDefine_bytes_blob_bufferSizeMinusOne_session(t *testing.T) {
	sc := ora.NewStmtCfg()
	testBindDefine(gen_bytes(sc.LobBufferSize()-1), blob, t, nil, ora.Bin)
}

func TestBindDefine_bytes_blob_bufferSizePlusOne_session(t *testing.T) {
	sc := ora.NewStmtCfg()
	testBindDefine(gen_bytes(sc.LobBufferSize()+1), blob, t, nil, ora.Bin)
}

func TestBindDefine_bytes_blob_bufferSizeMultiple_session(t *testing.T) {
	sc := ora.NewStmtCfg()
	testBindDefine(gen_bytes(sc.LobBufferSize()*3), blob, t, nil, ora.Bin)
}

func TestBindDefine_bytes_blob_bufferSizeMultipleMinusOne_session(t *testing.T) {
	sc := ora.NewStmtCfg()
	testBindDefine(gen_bytes((sc.LobBufferSize()*3)-1), blob, t, nil, ora.Bin)
}

func TestBindDefine_bytes_blob_bufferSizeMultiplePlusOne_session(t *testing.T) {
	sc := ora.NewStmtCfg()
	testBindDefine(gen_bytes((sc.LobBufferSize()*3)+1), blob, t, nil, ora.Bin)
}

func TestBindDefine_OraBytesLob_blob_bufferSize_session(t *testing.T) {
	//enableLogging(t)
	sc := ora.NewStmtCfg()
	testBindDefine(gen_OraBytesLob(sc.LobBufferSize(), false), blob, t, nil, ora.Bin)
}

func TestBindDefine_OraBytesLobPtr_blob_bufferSize_session(t *testing.T) {
	//enableLogging(t)
	sc := ora.NewStmtCfg()
	lob := gen_OraBytesLob(sc.LobBufferSize(), false)
	testBindDefine(&lob, blob, t, nil, ora.Bin)
}

func TestBindDefine_OraBytesLob_blob_bufferSizeMinusOne_session(t *testing.T) {
	//enableLogging(t)
	sc := ora.NewStmtCfg()
	testBindDefine(gen_OraBytesLob(sc.LobBufferSize()-1, false), blob, t, nil, ora.Bin)
}

func TestBindDefine_OraBytesLob_blob_bufferSizePlusOne_session(t *testing.T) {
	//enableLogging(t)
	sc := ora.NewStmtCfg()
	testBindDefine(gen_OraBytesLob(sc.LobBufferSize()+1, false), blob, t, nil, ora.Bin)
}

func TestBindDefine_OraBytesLob_blob_bufferSizeMultiple_session(t *testing.T) {
	//enableLogging(t)
	sc := ora.NewStmtCfg()
	testBindDefine(gen_OraBytesLob(sc.LobBufferSize()*3, false), blob, t, nil, ora.Bin)
}

func TestBindDefine_OraBytesLob_blob_bufferSizeMultipleMinusOne_session(t *testing.T) {
	//enableLogging(t)
	sc := ora.NewStmtCfg()
	testBindDefine(gen_OraBytesLob((sc.LobBufferSize()*3)-1, false), blob, t, nil, ora.Bin)
}

func TestBindDefine_OraBytesLob_blob_bufferSizeMultiplePlusOne_session(t *testing.T) {
	//enableLogging(t)
	sc := ora.NewStmtCfg()
	testBindDefine(gen_OraBytesLob((sc.LobBufferSize()*3)+1, false), blob, t, nil, ora.Bin)
}

func TestBindDefine_OraBytesLobPtr_blob_bufferSizeMultiplePlusOne_session(t *testing.T) {
	//enableLogging(t)
	sc := ora.NewStmtCfg()
	lob := gen_OraBytesLob((sc.LobBufferSize()*3)+1, false)
	testBindDefine(&lob, blob, t, nil, ora.Bin)
}

////////////////////////////////////////////////////////////////////////////////
// blobNull
////////////////////////////////////////////////////////////////////////////////
func TestBindDefine_bytes_blobNull_session(t *testing.T) {
	testBindDefine(gen_bytes(9), blobNull, t, nil, ora.Bin)
}

func TestBindDefine_OraBytes_blobNull_session(t *testing.T) {
	testBindDefine(gen_OraBytes(9, true), blobNull, t, nil, ora.OraBin)
}

func TestBindDefine_OraBytesLob_blobNull_session(t *testing.T) {
	//enableLogging(t)
	testBindDefine(gen_OraBytesLob(9, true), blobNull, t, nil, ora.OraBin)
}

func TestBindDefine_OraBytesLobPtr_blobNull_session(t *testing.T) {
	//enableLogging(t)
	lob := gen_OraBytesLob(9, true)
	testBindDefine(&lob, blobNull, t, nil, ora.OraBin)
}

func TestBindSlice_bytes_blobNull_session(t *testing.T) {
	testBindDefine(gen_bytesSlice(9), blobNull, t, nil)
}

func TestBindSlice_OraBytes_blobNull_session(t *testing.T) {
	testBindDefine(gen_OraBytesSlice(9, true), blobNull, t, nil)
}

func TestMultiDefine_blobNull_session(t *testing.T) {
	testMultiDefine(gen_bytes(9), blobNull, t)
}

func TestWorkload_blobNull_session(t *testing.T) {
	testWorkload(blobNull, t)
}

func TestBindDefine_blobNull_nil_session(t *testing.T) {
	testBindDefine(nil, blobNull, t, nil)
}
<commit_msg>modularize z_bytes_session_test<commit_after>//Copyright 2014 Rana Ian. All rights reserved.
//Use of this source code is governed by The MIT License
//found in the accompanying LICENSE file.

package ora_test

import (
	"fmt"
	"strings"
	"testing"

	"gopkg.in/rana/ora.v3"
)

//// bytes
//longRaw     oracleColumnType = "long raw not null"
//longRawNull oracleColumnType = "long raw null"
//raw2000     oracleColumnType = "raw(2000) not null"
//raw2000Null oracleColumnType = "raw(2000) null"
//blob        oracleColumnType = "blob not null"
//blobNull    oracleColumnType = "blob null"
var _T_bytesCols = []string{
	"longRaw", "longRawNull",
	"raw2000", "raw2000Null",
	"blob", "blobNull",
}

func TestBindDefine_bytes(t *testing.T) {
	sc := ora.NewStmtCfg()
	type testCase struct {
		gen func() interface{}
		ct  oracleColumnType
		gct ora.GoColumnType
	}
	testCases := make(map[string]testCase)
	for _, ctName := range _T_bytesCols {
		for _, typName := range []string{
			"bytes", "OraBytes", "OraBytesLob",
			"bytes2000", "OraBytes2000",
		} {
			testCases[typName+"_"+ctName] = testCase{
				gen: _T_bytesGen[typName],
				ct:  _T_colType[ctName],
			}
		}
	}
	for name, tc := range testCases {
		gct := ora.Bin
		if strings.HasPrefix(name, "Ora") {
			gct = ora.OraBin
		}
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			testBindDefine(tc.gen(), tc.ct, t, sc, tc.gct)
		})
	}
}

func TestBindSlice_bytes(t *testing.T) {
	sc := ora.NewStmtCfg()
	type testCase struct {
		ct  oracleColumnType
		gen func() interface{}
	}
	testCases := make(map[string]testCase)
	for _, typName := range []string{
		"bytesSlice", "OraBytesSlice",
		"bytesSlice2000", "OraBytesSlice2000",
	} {
		for _, ctName := range _T_bytesCols {
			if strings.HasSuffix(ctName, "Null") {
				typName += "_null"
			}
			testCases[typName+"_"+ctName] = testCase{
				ct:  _T_colType[ctName],
				gen: _T_bytesGen[typName],
			}
		}
	}
	for name, tc := range testCases {
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			testBindDefine(tc.gen(), tc.ct, t, sc)
		})
	}
}

func TestMultiDefine_bytes(t *testing.T) {
	for _, ctName := range _T_bytesCols {
		t.Run(ctName, func(t *testing.T) {
			t.Parallel()
			//enableLogging(t)
			testMultiDefine(gen_bytes(9), _T_colType[ctName], t)
		})
	}
}

func TestWorkload_bytes(t *testing.T) {
	for _, ctName := range []string{"raw2000", "raw2000Null", "blob", "blobNull"} {
		t.Run(ctName, func(t *testing.T) {
			t.Parallel()
			testWorkload(_T_colType[ctName], t)
		})
	}
}

func TestBindDefine_bytes_nil(t *testing.T) {
	sc := ora.NewStmtCfg()
	for _, ctName := range []string{"longRawNull", "raw2000Null", "blobNull"} {
		t.Run(ctName, func(t *testing.T) {
			t.Parallel()
			testBindDefine(nil, _T_colType[ctName], t, sc)
		})
	}
}

var _T_bytesGen = map[string](func() interface{}){
	"bytes":     func() interface{} { return gen_bytes(9) },
	"bytes2000": func() interface{} { return gen_bytes(2000) },

	"OraBytes":          func() interface{} { return gen_OraBytes(9, false) },
	"OraBytes2000":      func() interface{} { return gen_OraBytes(2000, false) },
	"OraBytes_null":     func() interface{} { return gen_OraBytes(9, true) },
	"OraBytes2000_null": func() interface{} { return gen_OraBytes(2000, true) },
	"OraBytesLob":       func() interface{} { return gen_OraBytesLob(9, false) },

	"bytesSlice":             func() interface{} { return gen_bytesSlice(9) },
	"bytesSlice2000":         func() interface{} { return gen_bytesSlice(2000) },
	"OraBytesSlice":          func() interface{} { return gen_OraBytesSlice(9, false) },
	"OraBytesSlice2000":      func() interface{} { return gen_OraBytesSlice(2000, false) },
	"OraBytesSlice_null":     func() interface{} { return gen_OraBytesSlice(9, true) },
	"OraBytesSlice2000_null": func() interface{} { return gen_OraBytesSlice(2000, true) },
}

//// Do not test workload of multiple Oracle LONG RAW types within the same table because
//// ORA-01754: a table may contain only one column of type LONG
//func TestWorkload_longRaw_session(t *testing.T) {
//	testWorkload(testWorkloadColumnCount, t, longRaw)
//}

//// Do not test workload of multiple Oracle LONG RAW types within the same table because
//// ORA-01754: a table may contain only one column of type LONG
//func TestWorkload_longRawNull_session(t *testing.T) {
//	testWorkload(testWorkloadColumnCount, t, longRawNull)
//}

func TestBindDefine_bytes_blob_size(t *testing.T) {
	sc := ora.NewStmtCfg()
	lbs := sc.LobBufferSize()
	for _, size := range []int{
		lbs - 1,
		lbs,
		lbs + 1,
		lbs*3 - 1,
		lbs * 3,
		lbs*3 + 1,
	} {
		t.Run(fmt.Sprintf("%d", size), func(t *testing.T) {
			testBindDefine(gen_bytes(size), blob, t, sc, ora.Bin)
			testBindDefine(gen_OraBytesLob(size, false), blob, t, sc, ora.Bin)
			lob := gen_OraBytesLob(size, false)
			testBindDefine(&lob, blob, t, sc, ora.Bin)
		})
	}
}
<|endoftext|>