<commit_before>// Package meetup implements a cache with meetup and smart revalidation.
//
// A meetup cache will coalesce concurrent requests for the same item, so that
// only one request will be done to the backend per entry.
//
// In addition, this package implements a rich expiration state machine which
// helps to avoid latency spikes and provides backpressure when the backend
// returns errors.
//
// For example, if you have a dynamic proxy which proxies many hostnames to
// different places, you'll want an Options configuration similar to this:
//     op := Options{
//         Get: ...,
//
//         // Don't overload the backend with more than 50 requests at a time
//         Concurrency: 50,
//
//         // Keep errors for 2 seconds so that a failed backend doesn't get
//         // slammed with huge request load, and so that missing mappings don't
//         // cause each user request to cause a backend request.
//         //
//         // Additionally, keeping ErrorAge relatively low means that we can
//         // recover from backend failures somewhat quickly.
//         ErrorAge: 2 * time.Second,
//
//         // Keep entries for a long time so that a failed backend doesn't
//         // cause production traffic to quickly fail. We handle updating
//         // mappings with revalidation instead.
//         ExpireAge: time.Hour,
//
//         // Revalidate entries regularly so that we know their mapping points
//         // to the right place.
//         RevalidateAge: time.Minute,
//
//         // Keep the cache from taking up too much memory.
//         MaxSize: 10000,
//     }
package meetup

import (
	"errors"
	"fmt"
	"io"
	"sync"
	"time"

	"gopkg.in/tomb.v2"
)

var (
	// ErrClosed is returned by Cache.Get when the Cache has been Closed.
	ErrClosed = errors.New("Cache Closed")
)

var (
	// now is a variable so that expiration and revalidation tests can be written
	// deterministically.
	now = time.Now

	// postGetCheckCh, if non-nil, has one value written to it per (*Cache).Get
	// that finishes all its checks and starts any fill calls.
	postGetCheckCh chan struct{}

	// fillComplete, if non-nil, has one value written to it per (*Cache).fill
	// that completes.
	fillComplete chan struct{}
)

// Options control the behavior of the Cache with respect to its backend.
type Options struct {
	// When a key is requested that does not exist in the cache (or needs to be
	// revalidated) then Get will be called. Get is called concurrently, at most
	// once concurrently per concurrent key requested.
	Get func(key string) (interface{}, error)

	// If greater than zero, only Concurrency Get calls will be done
	// concurrently. Any other calls will wait until one of the running Get calls
	// complete.
	Concurrency int

	// If an Options.Get returns an error, cache the error for this amount of
	// time. If negative or zero, don't cache errors.
	ErrorAge time.Duration

	// Once an entry's age reaches ExpireAge, it is considered expired and the
	// cached result will not be used. If set to zero, values do not expire.
	ExpireAge time.Duration

	// Once an entry's age reaches RevalidateAge, a background Options.Get will
	// be made on its key, but Cache.Get will continue to return immediately. If
	// the background Get returns an error, it will not be retried until
	// ErrorAge has passed since the last revalidation attempt. During this
	// process, the existing entry will continue to be returned from Cache.Get
	// as long as it has not expired.
	//
	// If zero, revalidation is disabled.
	RevalidateAge time.Duration

	// If MaxSize is greater than zero, then when Options.Get returns, some of
	// the values not used most recently will be evicted from the cache until
	// the total size of all values is underneath MaxSize.
	//
	// Currently running Gets do not count towards MaxSize.
	MaxSize uint64

	// ItemSize is called to figure out the size of a value to compare against
	// MaxSize. If ItemSize is not set or returns a zero, the size of a value is
	// assumed to be 1.
	ItemSize func(key string, value interface{}) uint64
}

// Stats are returned from Cache.Stats.
type Stats struct {
	// The number of times Cache.Get returned cached data.
	Hits uint64

	// The number of times Cache.Get did not find an item in the cache.
	Misses uint64

	// The number of values evicted from the cache.
	Evictions uint64

	// The number of times a value was revalidated in-place.
	Revalidations uint64

	// The number of times a value was found in the cache, but had expired.
	// NB: This counter is not updated when a value expires, only when it is
	// found in the cache after it has already expired.
	Expires uint64

	// The number of errors (err != nil) returned from Options.Get.
	Errors uint64

	// The current size of the cache.
	CurrentSize uint64

	// The current number of items in the cache.
	CurrentCount uint64
}

// Cache implements a meetup cache.
type Cache struct {
	t tomb.Tomb

	o Options

	concLimitCh chan struct{}

	mu            sync.Mutex
	tree          *tree
	evictAt       *enumerator
	expireCheckAt *enumerator
	totalSize     uint64
	stats         Stats
}

type entry struct {
	ReadyCond *sync.Cond

	Size uint64

	LastUpdate          time.Time
	DontRevalidateUntil time.Time

	// Value and Error are valid iff Ready is true
	Value interface{}
	Error error

	// Filling is true iff a fill is running for this entry
	Filling bool

	// Only set this through SetReady.
	Ready bool

	// RecentlyUsed is true iff a Get has hit this key since the last eviction
	// cycle hit it (or since it was created.)
	RecentlyUsed bool
}

// New returns a Cache with the given Options.
func New(o Options) *Cache {
	c := &Cache{
		o:    o,
		tree: treeNew(),
	}

	if o.Concurrency > 0 {
		c.concLimitCh = make(chan struct{}, o.Concurrency)
	}

	// Keep the tomb alive for future c.fill calls
	c.t.Go(func() error {
		<-c.t.Dying()
		return nil
	})

	return c
}

func (c *Cache) setEntryValue(key string, e *entry, value interface{}, err error) {
	e.Value = value
	e.Error = err
	e.DontRevalidateUntil = time.Time{}

	newSize := uint64(1)
	if c.o.ItemSize != nil {
		sz := c.o.ItemSize(key, value)
		if sz > 0 {
			newSize = sz
		}
	}

	c.totalSize += newSize - e.Size

	e.Size = newSize

	if !e.Ready {
		e.Ready = true
		e.ReadyCond.Broadcast()
	}
}

func (c *Cache) setEntryCleared(e *entry) {
	if e.Ready {
		e.Value = nil
		e.Error = nil
		c.totalSize -= e.Size
		e.Size = 0
		e.Ready = false
		e.ReadyCond.Broadcast()
	}
}

// Get retrieves an entry's value from the cache, calling Options.Get if needed
// to fill the cache. If multiple concurrent Get calls occur on the same key,
// all of them will recieve the return value of a single Options.Get call.
func (c *Cache) Get(key string) (interface{}, error) {
	select {
	case <-c.t.Dying():
		return nil, ErrClosed
	default:
	}

	t := now()

	fillInline := false

	c.mu.Lock()
	e, ok := c.tree.Get(key)
	if ok {
		e.RecentlyUsed = true
	} else {
		// No entry for this key. Create the entry.
		e = &entry{
			ReadyCond: sync.NewCond(&c.mu),
		}
		fillInline = true
		c.tree.Set(key, e)
		c.stats.Misses++
	}

	if e.Ready {
		age := t.Sub(e.LastUpdate)
		if c.o.ExpireAge > 0 && age >= c.o.ExpireAge {
			c.stats.Expires++
			c.setEntryCleared(e)
			fillInline = true
		} else if e.Error != nil && (c.o.ErrorAge <= 0 || age >= c.o.ErrorAge) {
			c.setEntryCleared(e)
			fillInline = true
		} else if c.o.RevalidateAge > 0 && age >= c.o.RevalidateAge &&
			(t.Equal(e.DontRevalidateUntil) || t.After(e.DontRevalidateUntil)) {

			c.stats.Revalidations++
			c.startFill(key, e)
		}
		if e.Ready {
			c.stats.Hits++
		}
	}

	// Used for the test suite.
	if postGetCheckCh != nil {
		postGetCheckCh <- struct{}{}
	}

	if fillInline {
		if e.Ready || e.Filling {
			panic("not reached")
		}
		e.Filling = true
		c.mu.Unlock()
		c.fill(key, e)
		c.mu.Lock()
	}

	for !e.Ready {
		e.ReadyCond.Wait()
	}

	value := e.Value
	err := e.Error

	c.mu.Unlock()

	if err != nil {
		return nil, err
	}
	return value, nil
}

// Stats retrieves the current stats for the Cache.
func (c *Cache) Stats() Stats {
	c.mu.Lock()
	st := c.stats
	st.CurrentSize = c.totalSize
	st.CurrentCount = uint64(c.tree.Len())
	c.mu.Unlock()
	return st
}

func (c *Cache) startFill(key string, e *entry) {
	if e.Filling {
		return
	}

	e.Filling = true
	c.t.Go(func() error {
		c.fill(key, e)
		return nil
	})
}

func (c *Cache) fill(key string, e *entry) {

	// Used for the test suite only.
	if fillComplete != nil {
		defer func() { fillComplete <- struct{}{} }()
	}

	if c.concLimitCh != nil {
		c.concLimitCh <- struct{}{}
		defer func() { <-c.concLimitCh }()
	}

	t := now()
	value, err := c.o.Get(key)

	c.mu.Lock()

	if !e.Ready || err == nil {
		e.LastUpdate = t
		c.setEntryValue(key, e, value, err)
	}

	if e.Ready && err != nil && c.o.ErrorAge > 0 {
		e.DontRevalidateUntil = t.Add(c.o.ErrorAge)
	}

	e.Filling = false

	if err != nil {
		c.stats.Errors++
	}

	if c.o.ExpireAge > 0 {
		c.expireCheckStep(t)
	}

	if c.o.MaxSize > 0 {
		if e.Size > c.o.MaxSize {
			// Rather than evict our entire cache and STILL not have room for
			// this value, we just evict this value immediately.
			c.tree.Delete(key)
			c.totalSize -= e.Size
		} else {
			c.evictCheck(e)
		}
	}

	c.mu.Unlock()
}

func (c *Cache) expireCheckStep(t time.Time) {
	for i := 0; i < 2; i++ {
		var (
			k   string
			v   *entry
			err error = io.EOF
		)
		if c.expireCheckAt != nil {
			k, v, err = c.expireCheckAt.Next()
		}
		if err == io.EOF {
			c.expireCheckAt, err = c.tree.SeekFirst()
			if err == io.EOF {
				// Tree is empty
				return
			}
			continue
		}

		if v.Ready && !v.Filling {
			age := t.Sub(v.LastUpdate)
			if age >= c.o.ExpireAge {
				c.stats.Expires++
				c.tree.Delete(k)
				c.totalSize -= v.Size
			}
		}
	}
}

func (c *Cache) evictCheck(invulnerableEntry *entry) {
	var (
		k   string
		v   *entry
		err error = io.EOF
	)
	for c.totalSize > c.o.MaxSize {
		if c.evictAt != nil {
			k, v, err = c.evictAt.Next()
		}
		if err == io.EOF {
			c.evictAt, err = c.tree.SeekFirst()
			if err == io.EOF {
				// Tree is empty. Shouldn't ever occur, but we can
				// safely just bail out of the eviction loop.
				break
			}
			continue
		}

		if v == invulnerableEntry {
			// Never attempt to evict the invulnerable entry that was just
			// inserted.
			continue
		}

		if v.RecentlyUsed {
			v.RecentlyUsed = false
			continue
		}

		if v.Ready {
			if v.Filling {
				// We shouldn't evict keys that are filling by
				// deleting them from the map; instead, we should
				// keep them around but remove their data. This
				// allows future Cache.Gets to meet up with the
				// existing fill routine.
				c.setEntryCleared(v)
			} else {
				c.tree.Delete(k)
				c.totalSize -= v.Size
			}
			c.stats.Evictions++
		}
	}
}

// Close waits for all running Options.Get calls to finish and makes all future
// Cache.Get calls return ErrClosed.
func (c *Cache) Close() error {
	c.t.Kill(nil)
	return c.t.Wait()
}

func (c *Cache) validateTotalSize() {
	c.mu.Lock()
	defer c.mu.Unlock()

	size := uint64(0)
	enum, err := c.tree.SeekFirst()
	if err != io.EOF {
		for {
			_, v, err := enum.Next()
			if err == io.EOF {
				break
			}
			size += v.Size
		}
	}

	if size != c.totalSize {
		panic(fmt.Sprintf("c.totalSize = %v, but calculated sum = %v", c.totalSize, size))
	}
}
<commit_msg>use a sync.Pool for *entry values to avoid steady-state allocations; 15% faster in real-world benchmarks<commit_after>// Package meetup implements a cache with meetup and smart revalidation.
//
// A meetup cache will coalesce concurrent requests for the same item, so that
// only one request will be done to the backend per entry.
//
// In addition, this package implements a rich expiration state machine which
// helps to avoid latency spikes and provides backpressure when the backend
// returns errors.
//
// For example, if you have a dynamic proxy which proxies many hostnames to
// different places, you'll want an Options configuration similar to this:
//     op := Options{
//         Get: ...,
//
//         // Don't overload the backend with more than 50 requests at a time
//         Concurrency: 50,
//
//         // Keep errors for 2 seconds so that a failed backend doesn't get
//         // slammed with huge request load, and so that missing mappings don't
//         // cause each user request to cause a backend request.
//         //
//         // Additionally, keeping ErrorAge relatively low means that we can
//         // recover from backend failures somewhat quickly.
//         ErrorAge: 2 * time.Second,
//
//         // Keep entries for a long time so that a failed backend doesn't
//         // cause production traffic to quickly fail. We handle updating
//         // mappings with revalidation instead.
//         ExpireAge: time.Hour,
//
//         // Revalidate entries regularly so that we know their mapping points
//         // to the right place.
//         RevalidateAge: time.Minute,
//
//         // Keep the cache from taking up too much memory.
//         MaxSize: 10000,
//     }
package meetup

import (
	"errors"
	"fmt"
	"io"
	"sync"
	"time"

	"gopkg.in/tomb.v2"
)

var (
	// ErrClosed is returned by Cache.Get when the Cache has been Closed.
	ErrClosed = errors.New("Cache Closed")
)

var (
	// now is a variable so that expiration and revalidation tests can be written
	// deterministically.
	now = time.Now

	// postGetCheckCh, if non-nil, has one value written to it per (*Cache).Get
	// that finishes all its checks and starts any fill calls.
	postGetCheckCh chan struct{}

	// fillComplete, if non-nil, has one value written to it per (*Cache).fill
	// that completes.
	fillComplete chan struct{}
)

// Options control the behavior of the Cache with respect to its backend.
type Options struct {
	// When a key is requested that does not exist in the cache (or needs to be
	// revalidated) then Get will be called. Get is called concurrently, at most
	// once concurrently per concurrent key requested.
	Get func(key string) (interface{}, error)

	// If greater than zero, only Concurrency Get calls will be done
	// concurrently. Any other calls will wait until one of the running Get calls
	// complete.
	Concurrency int

	// If an Options.Get returns an error, cache the error for this amount of
	// time. If negative or zero, don't cache errors.
	ErrorAge time.Duration

	// Once an entry's age reaches ExpireAge, it is considered expired and the
	// cached result will not be used. If set to zero, values do not expire.
	ExpireAge time.Duration

	// Once an entry's age reaches RevalidateAge, a background Options.Get will
	// be made on its key, but Cache.Get will continue to return immediately. If
	// the background Get returns an error, it will not be retried until
	// ErrorAge has passed since the last revalidation attempt. During this
	// process, the existing entry will continue to be returned from Cache.Get
	// as long as it has not expired.
	//
	// If zero, revalidation is disabled.
	RevalidateAge time.Duration

	// If MaxSize is greater than zero, then when Options.Get returns, some of
	// the values not used most recently will be evicted from the cache until
	// the total size of all values is underneath MaxSize.
	//
	// Currently running Gets do not count towards MaxSize.
	MaxSize uint64

	// ItemSize is called to figure out the size of a value to compare against
	// MaxSize. If ItemSize is not set or returns a zero, the size of a value is
	// assumed to be 1.
	ItemSize func(key string, value interface{}) uint64
}

// Stats are returned from Cache.Stats.
type Stats struct {
	// The number of times Cache.Get returned cached data.
	Hits uint64

	// The number of times Cache.Get did not find an item in the cache.
	Misses uint64

	// The number of values evicted from the cache.
	Evictions uint64

	// The number of times a value was revalidated in-place.
	Revalidations uint64

	// The number of times a value was found in the cache, but had expired.
	// NB: This counter is not updated when a value expires, only when it is
	// found in the cache after it has already expired.
	Expires uint64

	// The number of errors (err != nil) returned from Options.Get.
	Errors uint64

	// The current size of the cache.
	CurrentSize uint64

	// The current number of items in the cache.
	CurrentCount uint64
}

// Cache implements a meetup cache.
type Cache struct {
	t tomb.Tomb

	o Options

	concLimitCh chan struct{}

	entryPool *sync.Pool

	mu            sync.Mutex
	tree          *tree
	evictAt       *enumerator
	expireCheckAt *enumerator
	totalSize     uint64
	stats         Stats
}

type entry struct {
	ReadyCond *sync.Cond

	Size uint64

	LastUpdate          time.Time
	DontRevalidateUntil time.Time

	// Value and Error are valid iff Ready is true
	Value interface{}
	Error error

	// ReadersWaiting is a counter of the number of *Cache.Gets which have a
	// pointer to this entry and intend to read its Value and Error fields.
	ReadersWaiting uint32

	// Filling is true iff a fill is running for this entry
	Filling bool

	// Only set this through SetReady.
	Ready bool

	// RecentlyUsed is true iff a Get has hit this key since the last eviction
	// cycle hit it (or since it was created.)
	RecentlyUsed bool
}

// New returns a Cache with the given Options.
func New(o Options) *Cache {
	c := &Cache{
		o:    o,
		tree: treeNew(),
	}

	c.entryPool = &sync.Pool{
		New: func() interface{} {
			return &entry{
				ReadyCond: sync.NewCond(&c.mu),
			}
		},
	}

	if o.Concurrency > 0 {
		c.concLimitCh = make(chan struct{}, o.Concurrency)
	}

	// Keep the tomb alive for future c.fill calls
	c.t.Go(func() error {
		<-c.t.Dying()
		return nil
	})

	return c
}

func (c *Cache) doneWithEntry(e *entry) {
	if e.Filling {
		panic("cannot be done with an entry that is filling")
	}

	if e.ReadersWaiting != 0 {
		// This entry will have its Value and Error fields read later; it must
		// not be cleared before then. Therefore, we just drop it on the floor
		// and let the GC handle it.
		//
		// Performance is still relatively good without entry pooling, so it's
		// not terrible to create a little garbage.
		return
	}

	// Zero all fields except ReadyCond
	*e = entry{ReadyCond: e.ReadyCond}

	c.entryPool.Put(e)
}

func (c *Cache) getNewEntry() *entry {
	return c.entryPool.Get().(*entry)
}

func (c *Cache) setEntryValue(key string, e *entry, value interface{}, err error) {
	e.Value = value
	e.Error = err
	e.DontRevalidateUntil = time.Time{}

	newSize := uint64(1)
	if c.o.ItemSize != nil {
		sz := c.o.ItemSize(key, value)
		if sz > 0 {
			newSize = sz
		}
	}

	c.totalSize += newSize - e.Size

	e.Size = newSize

	if !e.Ready {
		e.Ready = true
		e.ReadyCond.Broadcast()
	}
}

func (c *Cache) setEntryCleared(e *entry) {
	if e.Ready {
		e.Value = nil
		e.Error = nil
		c.totalSize -= e.Size
		e.Size = 0
		e.Ready = false
		e.ReadyCond.Broadcast()
	}
}

// Get retrieves an entry's value from the cache, calling Options.Get if needed
// to fill the cache. If multiple concurrent Get calls occur on the same key,
// all of them will recieve the return value of a single Options.Get call.
func (c *Cache) Get(key string) (interface{}, error) {
	select {
	case <-c.t.Dying():
		return nil, ErrClosed
	default:
	}

	t := now()

	fillInline := false

	c.mu.Lock()
	e, ok := c.tree.Get(key)
	if ok {
		e.RecentlyUsed = true
	} else {
		// No entry for this key. Create the entry.
		e = c.getNewEntry()
		fillInline = true
		c.tree.Set(key, e)
		c.stats.Misses++
	}

	e.ReadersWaiting++

	if e.Ready {
		age := t.Sub(e.LastUpdate)
		if c.o.ExpireAge > 0 && age >= c.o.ExpireAge {
			c.stats.Expires++
			c.setEntryCleared(e)
			fillInline = true
		} else if e.Error != nil && (c.o.ErrorAge <= 0 || age >= c.o.ErrorAge) {
			c.setEntryCleared(e)
			fillInline = true
		} else if c.o.RevalidateAge > 0 && age >= c.o.RevalidateAge &&
			(t.Equal(e.DontRevalidateUntil) || t.After(e.DontRevalidateUntil)) {

			c.stats.Revalidations++
			c.startFill(key, e)
		}
		if e.Ready {
			c.stats.Hits++
		}
	}

	// Used for the test suite.
	if postGetCheckCh != nil {
		postGetCheckCh <- struct{}{}
	}

	if fillInline {
		if e.Ready || e.Filling {
			panic("not reached")
		}
		e.Filling = true
		c.mu.Unlock()
		c.fill(key, e)
		c.mu.Lock()
	}

	for !e.Ready {
		e.ReadyCond.Wait()
	}

	value := e.Value
	err := e.Error

	e.ReadersWaiting--

	c.mu.Unlock()

	if err != nil {
		return nil, err
	}
	return value, nil
}

// Stats retrieves the current stats for the Cache.
func (c *Cache) Stats() Stats {
	c.mu.Lock()
	st := c.stats
	st.CurrentSize = c.totalSize
	st.CurrentCount = uint64(c.tree.Len())
	c.mu.Unlock()
	return st
}

func (c *Cache) startFill(key string, e *entry) {
	if e.Filling {
		return
	}

	e.Filling = true
	c.t.Go(func() error {
		c.fill(key, e)
		return nil
	})
}

func (c *Cache) fill(key string, e *entry) {

	// Used for the test suite only.
	if fillComplete != nil {
		defer func() { fillComplete <- struct{}{} }()
	}

	if c.concLimitCh != nil {
		c.concLimitCh <- struct{}{}
		defer func() { <-c.concLimitCh }()
	}

	t := now()
	value, err := c.o.Get(key)

	c.mu.Lock()

	if !e.Ready || err == nil {
		e.LastUpdate = t
		c.setEntryValue(key, e, value, err)
	}

	if e.Ready && err != nil && c.o.ErrorAge > 0 {
		e.DontRevalidateUntil = t.Add(c.o.ErrorAge)
	}

	e.Filling = false

	if err != nil {
		c.stats.Errors++
	}

	if c.o.ExpireAge > 0 {
		c.expireCheckStep(t)
	}

	if c.o.MaxSize > 0 {
		if e.Size > c.o.MaxSize {
			// Rather than evict our entire cache and STILL not have room for
			// this value, we just evict this value immediately.
			c.tree.Delete(key)
			c.totalSize -= e.Size
		} else {
			c.evictCheck(e)
		}
	}

	c.mu.Unlock()
}

func (c *Cache) expireCheckStep(t time.Time) {
	for i := 0; i < 2; i++ {
		var (
			k   string
			v   *entry
			err error = io.EOF
		)
		if c.expireCheckAt != nil {
			k, v, err = c.expireCheckAt.Next()
		}
		if err == io.EOF {
			c.expireCheckAt, err = c.tree.SeekFirst()
			if err == io.EOF {
				// Tree is empty
				return
			}
			continue
		}

		if v.Ready && !v.Filling {
			age := t.Sub(v.LastUpdate)
			if age >= c.o.ExpireAge {
				c.stats.Expires++
				c.tree.Delete(k)
				c.totalSize -= v.Size
				c.doneWithEntry(v)
			}
		}
	}
}

func (c *Cache) evictCheck(invulnerableEntry *entry) {
	var (
		k   string
		v   *entry
		err error = io.EOF
	)
	for c.totalSize > c.o.MaxSize {
		if c.evictAt != nil {
			k, v, err = c.evictAt.Next()
		}
		if err == io.EOF {
			c.evictAt, err = c.tree.SeekFirst()
			if err == io.EOF {
				// Tree is empty. Shouldn't ever occur, but we can
				// safely just bail out of the eviction loop.
				break
			}
			continue
		}

		if v == invulnerableEntry {
			// Never attempt to evict the invulnerable entry that was just
			// inserted.
			continue
		}

		if v.RecentlyUsed {
			v.RecentlyUsed = false
			continue
		}

		if v.Ready {
			if v.Filling {
				// We shouldn't evict keys that are filling by
				// deleting them from the map; instead, we should
				// keep them around but remove their data. This
				// allows future Cache.Gets to meet up with the
				// existing fill routine.
				c.setEntryCleared(v)
			} else {
				c.tree.Delete(k)
				c.totalSize -= v.Size
				c.doneWithEntry(v)
			}
			c.stats.Evictions++
		}
	}
}

// Close waits for all running Options.Get calls to finish and makes all future
// Cache.Get calls return ErrClosed.
func (c *Cache) Close() error {
	c.t.Kill(nil)
	return c.t.Wait()
}

func (c *Cache) validateTotalSize() {
	c.mu.Lock()
	defer c.mu.Unlock()

	size := uint64(0)
	enum, err := c.tree.SeekFirst()
	if err != io.EOF {
		for {
			_, v, err := enum.Next()
			if err == io.EOF {
				break
			}
			size += v.Size
		}
	}

	if size != c.totalSize {
		panic(fmt.Sprintf("c.totalSize = %v, but calculated sum = %v", c.totalSize, size))
	}
}
<|endoftext|>


<commit_before><commit_msg>Styling and coding improvements<commit_after><|endoftext|>


<commit_before><commit_msg>Build assets<commit_after><|endoftext|>


<commit_before>package vhosts

import (
	"net/http"
	"sync"
)

type VirtualHosts struct {
	vhosts         map[string]http.Handler
	mu             sync.RWMutex
	defaultHandler http.Handler
}

func NewVirtualHosts(vhosts map[string]http.Handler) *VirtualHosts {
	v := &VirtualHosts{}
	for host, h := range vhosts {
		v.HandleHost(h, host)
	}
	return v
}

func (v *VirtualHosts) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	v.mu.RLock()
	defer v.mu.RUnlock()
	if v.vhosts != nil {
		if handler := v.vhosts[r.Host]; handler != nil {
			handler.ServeHTTP(w, r)
			return
		}
	}
	http.NotFound(w, r)
}

func (v *VirtualHosts) HandleHost(handler http.Handler, hosts ...string) {
	v.mu.Lock()
	defer v.mu.Unlock()
	if v.vhosts == nil {
		v.vhosts = make(map[string]http.Handler)
	}
	for _, host := range hosts {
		v.vhosts[host] = handler
	}
}
<commit_msg>Allow single-string multiple hosts in vhosts<commit_after>package vhosts

import (
	"net/http"
	"strings"
	"sync"
)

type VirtualHosts struct {
	vhosts map[string]http.Handler
	mu     sync.RWMutex
}

func NewVirtualHosts(vhosts map[string]http.Handler) *VirtualHosts {
	v := &VirtualHosts{}
	for hosts, h := range vhosts {
		for _, host := range strings.Split(hosts, " ") {
			if host != "" {
				v.HandleHost(h, host)
			}
		}
	}
	return v
}

func (v *VirtualHosts) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	v.mu.RLock()
	defer v.mu.RUnlock()
	if v.vhosts != nil {
		if handler, ok := v.vhosts[r.Host]; ok && handler != nil {
			handler.ServeHTTP(w, r)
			return
		}
	}
	http.NotFound(w, r)
}

func (v *VirtualHosts) HandleHost(handler http.Handler, hosts ...string) {
	v.mu.Lock()
	defer v.mu.Unlock()
	if v.vhosts == nil {
		v.vhosts = make(map[string]http.Handler)
	}
	for _, host := range hosts {
		for _, h := range strings.Split(host, " ") {
			if h = strings.TrimSpace(h); h != "" {
				v.vhosts[h] = handler
			}
		}
	}
}
<|endoftext|>


<commit_before>package rtda

import (
    "log"
    "strings"
    "jvmgo/any"
    rtc "jvmgo/jvm/rtda/class"
)

/*
JVM
  Thread
    pc
    Stack
      Frame
        LocalVars
        OperandStack
*/
type Thread struct {
    pc      int // the address of the instruction currently being executed
    stack   *Stack
    jThread *rtc.Obj // java.lang.Thread
    // todo
}

func NewThread(maxStackSize uint, jThread *rtc.Obj) (*Thread) {
    stack := newStack(maxStackSize)
    return &Thread{0, stack, jThread}
}

// getters & setters
func (self *Thread) PC() (int) {
    return self.pc
}
func (self *Thread) SetPC(pc int) {
    self.pc = pc
}
func (self *Thread) JThread() (*rtc.Obj) {
    return self.jThread
}
func (self *Thread) SetJThread(jThread *rtc.Obj) {
    self.jThread = jThread
}

func (self *Thread) IsStackEmpty() (bool) {
    return self.stack.isEmpty()
}
func (self *Thread) StackDepth() uint {
    return self.stack.size
}

func (self *Thread) CurrentFrame() (*Frame) {
    return self.stack.top()
}
func (self *Thread) TopFrame() (*Frame) {
    return self.stack.top()
}
func (self *Thread) TopFrameN(n uint) (*Frame) {
    return self.stack.topN(n)
}

func (self *Thread) PushFrame(frame *Frame) {
    self.stack.push(frame)
}
func (self *Thread) PopFrame() (*Frame) {
    top := self.stack.pop()
    if top.onPopAction != nil {
        // todo
        top.onPopAction()
    }
    return top
}

func (self *Thread) NewFrame(method *rtc.Method) (*Frame) {
    return newFrame(self, method)
}

func (self *Thread) InvokeMethod(method * rtc.Method) {
    //_logInvoke(self.stack.size, method)
    currentFrame := self.CurrentFrame()
    newFrame := self.NewFrame(method)
    self.PushFrame(newFrame)
    _passArgs(currentFrame.operandStack, newFrame.localVars, method.ActualArgCount())
}
func _passArgs(stack *OperandStack, vars *LocalVars, argCount uint) {
    if argCount > 0 {
        args := stack.popN(argCount)
        for i, j := uint(0), uint(0); i < argCount; i++ {
            arg := args[i]
            args[i] = nil
            vars.Set(i + j, arg)
            if any.IsLongOrDouble(arg) {
                j++
            }
        }
    }
}
func _logInvoke(stackSize uint, method * rtc.Method) {
    space := strings.Repeat(" ", int(stackSize))
    if method.IsStatic() {
        log.Printf("invoke method:%v %v.%v()", space, method.Class().Name(), method.Name())
    } else {
        log.Printf("invoke method:%v %v#%v()", space, method.Class().Name(), method.Name())
    }
}

// args not passed!
func (self *Thread) InvokeMethod2(method * rtc.Method) (*LocalVars) {
    //_logInvoke(self.stack.size, method)
    if !method.IsVoidReturnType() {
        // insert a garbage frame
        garbageMethod := rtc.NewGarbageMethod()
        garbageFrame := self.NewFrame(garbageMethod)
        self.PushFrame(garbageFrame)
    }

    newFrame := self.NewFrame(method)
    self.PushFrame(newFrame)
    return newFrame.localVars
}

// func (self *Thread) PushMethod(method *rtc.Method) (*Frame) {
//     frame := newFrame(self, method)
//     self.PushFrame(frame)
//     return frame
// }

func (self *Thread) HandleUncaughtException(ex *rtc.Obj) {
    self.stack.clear()
    sysClass := ex.Class().ClassLoader().LoadClass("java/lang/System")
    sysErr := sysClass.GetStaticValue("out", "Ljava/io/PrintStream;").(*rtc.Obj)
    printStackTrace := ex.Class().GetInstanceMethod("printStackTrace", "(Ljava/io/PrintStream;)V")

    // call ex.printStackTrace(System.err)
    newFrame := self.NewFrame(printStackTrace)
    vars := newFrame.localVars
    vars.SetRef(0, ex)
    vars.SetRef(1, sysErr)
    self.PushFrame(newFrame)

    //
    // printString := sysErr.Class().GetInstanceMethod()
}
<commit_msg>handle uncaught exception<commit_after>package rtda

import (
    "log"
    "strings"
    "jvmgo/any"
    rtc "jvmgo/jvm/rtda/class"
)

/*
JVM
  Thread
    pc
    Stack
      Frame
        LocalVars
        OperandStack
*/
type Thread struct {
    pc      int // the address of the instruction currently being executed
    stack   *Stack
    jThread *rtc.Obj // java.lang.Thread
    // todo
}

func NewThread(maxStackSize uint, jThread *rtc.Obj) (*Thread) {
    stack := newStack(maxStackSize)
    return &Thread{0, stack, jThread}
}

// getters & setters
func (self *Thread) PC() (int) {
    return self.pc
}
func (self *Thread) SetPC(pc int) {
    self.pc = pc
}
func (self *Thread) JThread() (*rtc.Obj) {
    return self.jThread
}
func (self *Thread) SetJThread(jThread *rtc.Obj) {
    self.jThread = jThread
}

func (self *Thread) IsStackEmpty() (bool) {
    return self.stack.isEmpty()
}
func (self *Thread) StackDepth() uint {
    return self.stack.size
}

func (self *Thread) CurrentFrame() (*Frame) {
    return self.stack.top()
}
func (self *Thread) TopFrame() (*Frame) {
    return self.stack.top()
}
func (self *Thread) TopFrameN(n uint) (*Frame) {
    return self.stack.topN(n)
}

func (self *Thread) PushFrame(frame *Frame) {
    self.stack.push(frame)
}
func (self *Thread) PopFrame() (*Frame) {
    top := self.stack.pop()
    if top.onPopAction != nil {
        // todo
        top.onPopAction()
    }
    return top
}

func (self *Thread) NewFrame(method *rtc.Method) (*Frame) {
    return newFrame(self, method)
}

func (self *Thread) InvokeMethod(method * rtc.Method) {
    //_logInvoke(self.stack.size, method)
    currentFrame := self.CurrentFrame()
    newFrame := self.NewFrame(method)
    self.PushFrame(newFrame)
    _passArgs(currentFrame.operandStack, newFrame.localVars, method.ActualArgCount())
}
func _passArgs(stack *OperandStack, vars *LocalVars, argCount uint) {
    if argCount > 0 {
        args := stack.popN(argCount)
        for i, j := uint(0), uint(0); i < argCount; i++ {
            arg := args[i]
            args[i] = nil
            vars.Set(i + j, arg)
            if any.IsLongOrDouble(arg) {
                j++
            }
        }
    }
}
func _logInvoke(stackSize uint, method * rtc.Method) {
    space := strings.Repeat(" ", int(stackSize))
    if method.IsStatic() {
        log.Printf("invoke method:%v %v.%v()", space, method.Class().Name(), method.Name())
    } else {
        log.Printf("invoke method:%v %v#%v()", space, method.Class().Name(), method.Name())
    }
}

// args not passed!
func (self *Thread) InvokeMethod2(method * rtc.Method) (*LocalVars) {
    //_logInvoke(self.stack.size, method)
    if !method.IsVoidReturnType() {
        // insert a garbage frame
        garbageMethod := rtc.NewGarbageMethod()
        garbageFrame := self.NewFrame(garbageMethod)
        self.PushFrame(garbageFrame)
    }

    newFrame := self.NewFrame(method)
    self.PushFrame(newFrame)
    return newFrame.localVars
}

// func (self *Thread) PushMethod(method *rtc.Method) (*Frame) {
//     frame := newFrame(self, method)
//     self.PushFrame(frame)
//     return frame
// }

func (self *Thread) HandleUncaughtException(ex *rtc.Obj) {
    self.stack.clear()
    sysClass := ex.Class().ClassLoader().LoadClass("java/lang/System")
    sysErr := sysClass.GetStaticValue("out", "Ljava/io/PrintStream;").(*rtc.Obj)
    printStackTrace := ex.Class().GetInstanceMethod("printStackTrace", "(Ljava/io/PrintStream;)V")

    // call ex.printStackTrace(System.err)
    newFrame := self.NewFrame(printStackTrace)
    vars := newFrame.localVars
    vars.SetRef(0, ex)
    vars.SetRef(1, sysErr)
    self.PushFrame(newFrame)

    //
    // printString := sysErr.Class().GetInstanceMethod("print", "(Ljava/lang/String;)V")
    // newFrame = self.NewFrame(printString)
    // vars = newFrame.localVars
    // vars.SetRef(0, sysErr)
    // vars.SetRef(1, NewJString("Exception in thread \"main\" ", newFrame))
    // self.PushFrame(newFrame)
}
<|endoftext|>


<commit_before>package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "os"
    "gopkg.in/alecthomas/kingpin.v1"
)

var (
    app = kingpin.New("substenv", "Substitute environment variables into templates")
    input = kingpin.Arg("input", "Input file or stdin if not given").File()
)

func main() {
    kingpin.Version("0.0.1")
    kingpin.Parse()

    var bio = bufio.NewReader(os.Stdin)
    for {
        var line, _, err = bio.ReadLine()
        if err != nil {
            if err != io.EOF {
                log.Fatal(err)
            }
            break
        }
        fmt.Printf("%s\n", line)
    }
}
<commit_msg>Regex for detecting envsubst constructs<commit_after>package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "os"
    "regexp"
    "gopkg.in/alecthomas/kingpin.v1"
)

var (
    app = kingpin.New("substenv", "Substitute environment variables into templates")
    input = app.Arg("input", "Input file or stdin if not given").File()
    re = regexp.MustCompile(`\$(?:\{([A-Z][A-Z0-9_]*)\}|([A-Z][A-Z0-9_]*)\b)`)
)

func main() {
    kingpin.Version("0.0.1")
    kingpin.Parse()

    var bio = bufio.NewReader(os.Stdin)
    for {
        var line, err = bio.ReadString('\n')
        if err != nil {
            if err != io.EOF {
                log.Fatal(err)
            }
            break
        }
        matches := re.FindAllStringSubmatchIndex(line, -1)
        fmt.Printf("%s -> %v\n", line, matches)
        for i := 0; i < len(matches); i++ {
            fmt.Printf("-- %v\n", matches[i])
        }
    }
}
<|endoftext|>


<commit_before>package supermath

import (
	"strconv"
)

// A Superint is a representation of a mathematical integer with an arbitrary
// size, i.e. an unlimited number of digits
type Superint struct {
	digits   []int
	negative bool
}

func (si Superint) String() string {
	var answer string
	for _, n := range si.digits {
		answer = string(strconv.Itoa(n)) + answer
	}
	if si.negative {
		answer = "-" + answer
	}
	return answer
}

// Take a string and return its corresponding Superint
func StringToSuperint(s string) Superint {
	var si Superint
	//TODO add validation later
	if s[0] == '-' {
		si.negative = true
		s = s[1:]
	}
	// "12345" would be stored as [5, 4, 3, 2, 1]
	for i := len(s) - 1; i >= 0; i-- {
		nextDigit, _ := strconv.Atoi(string(s[i]))
		si.digits = append(si.digits, nextDigit)
	}
	return si
}

// Take an int and return its corresponding Superint
func IntToSuperint(n int) Superint {
	return StringToSuperint(strconv.Itoa(n))
}

func SimplifySuperint(si Superint) Superint {
	var answer Superint
	answer.negative = si.negative
	carryover := 0
	for _, digit := range si.digits {
		answer.digits = append(answer.digits, (digit+carryover)%10)
		carryover = (digit + carryover) / 10
	}
	for ; carryover > 0; carryover /= 10 {
		answer.digits = append(answer.digits, carryover%10)
	}
	return answer
}

// for now, this only adds positive Superints
func AddSuperints(s1 Superint, s2 Superint) Superint {
	var answer Superint
	sizeDiff := len(s1.digits) - len(s2.digits)
	if sizeDiff > 0 {
		for i := 1; i <= sizeDiff; i++ {
			s2.digits = append(s2.digits, 0)
		}
	} else if sizeDiff < 0 {
		for i := -1; i >= sizeDiff; i-- {
			s1.digits = append(s1.digits, 0)
		}
	}
	for index := range s1.digits {
		answer.digits = append(answer.digits, s1.digits[index]+s2.digits[index])
	}
	return SimplifySuperint(answer)
}

func zeroSuperint() Superint {
	return Superint{digits: []int{0}, negative: false}
}

func copySuperint(s Superint) Superint {
	return Superint{digits: append([]int{}, s.digits...), negative: s.negative}
}

func SuperintMultiple(s Superint, n int) Superint {
	if n == 0 {
		return zeroSuperint()
	} else if n == 1 {
		return s
	}
	sCopy := copySuperint(s)
	for i, d := range sCopy.digits {
		sCopy.digits[i] = d * n
	}
	return SimplifySuperint(sCopy)
}

func IntPower(n int, power int) Superint {
	// TODO: confirm power >= 0, and not n == power == 0
	if power == 0 {
		return Superint{digits: []int{1}, negative: false}
	}
	s := IntToSuperint(n)
	for i := 2; i <= power; i++ {
		s = SuperintMultiple(s, n)
	}
	return s
}

func DigitSum(s Superint) int {
	answer := 0
	for _, num := range s.digits {
		answer += num
	}
	return answer
}

func DigitCount(s Superint) int {
	return len(s.digits)
}
<commit_msg>Export the ZeroSuperint() function<commit_after>package supermath

import (
	"strconv"
)

// A Superint is a representation of a mathematical integer with an arbitrary
// size, i.e. an unlimited number of digits
type Superint struct {
	digits   []int
	negative bool
}

func (si Superint) String() string {
	var answer string
	for _, n := range si.digits {
		answer = string(strconv.Itoa(n)) + answer
	}
	if si.negative {
		answer = "-" + answer
	}
	return answer
}

// Take a string and return its corresponding Superint
func StringToSuperint(s string) Superint {
	var si Superint
	//TODO add validation later
	if s[0] == '-' {
		si.negative = true
		s = s[1:]
	}
	// "12345" would be stored as [5, 4, 3, 2, 1]
	for i := len(s) - 1; i >= 0; i-- {
		nextDigit, _ := strconv.Atoi(string(s[i]))
		si.digits = append(si.digits, nextDigit)
	}
	return si
}

// Take an int and return its corresponding Superint
func IntToSuperint(n int) Superint {
	return StringToSuperint(strconv.Itoa(n))
}

func SimplifySuperint(si Superint) Superint {
	var answer Superint
	answer.negative = si.negative
	carryover := 0
	for _, digit := range si.digits {
		answer.digits = append(answer.digits, (digit+carryover)%10)
		carryover = (digit + carryover) / 10
	}
	for ; carryover > 0; carryover /= 10 {
		answer.digits = append(answer.digits, carryover%10)
	}
	return answer
}

// for now, this only adds positive Superints
func AddSuperints(s1 Superint, s2 Superint) Superint {
	var answer Superint
	sizeDiff := len(s1.digits) - len(s2.digits)
	if sizeDiff > 0 {
		for i := 1; i <= sizeDiff; i++ {
			s2.digits = append(s2.digits, 0)
		}
	} else if sizeDiff < 0 {
		for i := -1; i >= sizeDiff; i-- {
			s1.digits = append(s1.digits, 0)
		}
	}
	for index := range s1.digits {
		answer.digits = append(answer.digits, s1.digits[index]+s2.digits[index])
	}
	return SimplifySuperint(answer)
}

func ZeroSuperint() Superint {
	return Superint{digits: []int{0}, negative: false}
}

func copySuperint(s Superint) Superint {
	return Superint{digits: append([]int{}, s.digits...), negative: s.negative}
}

func SuperintMultiple(s Superint, n int) Superint {
	if n == 0 {
		return ZeroSuperint()
	} else if n == 1 {
		return s
	}
	sCopy := copySuperint(s)
	for i, d := range sCopy.digits {
		sCopy.digits[i] = d * n
	}
	return SimplifySuperint(sCopy)
}

func IntPower(n int, power int) Superint {
	// TODO: confirm power >= 0, and not n == power == 0
	if power == 0 {
		return Superint{digits: []int{1}, negative: false}
	}
	s := IntToSuperint(n)
	for i := 2; i <= power; i++ {
		s = SuperintMultiple(s, n)
	}
	return s
}

func DigitSum(s Superint) int {
	answer := 0
	for _, num := range s.digits {
		answer += num
	}
	return answer
}

func DigitCount(s Superint) int {
	return len(s.digits)
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package vm

import (
	"fmt"
	"regexp/syntax"
	"testing"
)

var typeUnificationTests = []struct {
	a, b     Type
	expected Type
}{
	// The unification of None with None is still None.
	{
		None, None,
		None,
	},
	// The unification of a type T with itself is T.
	{
		String, String,
		String,
	},
	{
		Int, Int,
		Int,
	},
	{
		Float, Float,
		Float,
	},
	{
		&TypeVariable{Id: 0}, &TypeVariable{Id: 0},
		&TypeVariable{Id: 0},
	},
	// The unification of any type operator with a type variable is the type operator
	{
		&TypeVariable{}, None,
		None,
	},
	{
		&TypeVariable{}, Float,
		Float,
	},
	{
		&TypeVariable{}, Int,
		Int,
	},
	{
		&TypeVariable{}, String,
		String,
	},
	{
		None, &TypeVariable{},
		None,
	},
	{
		Float, &TypeVariable{},
		Float,
	},
	{
		Int, &TypeVariable{},
		Int,
	},
	{
		String, &TypeVariable{},
		String,
	},
	// The lub of Int and Float is Float.
	{
		Int, Float,
		Float,
	},
	{
		Float, Int,
		Float,
	},
	// The lub of Int and String is String.
	{
		Int, String,
		String,
	},
	{
		String, Int,
		String,
	},
	// The lub of Float and String is String.
	{
		Float, String,
		String,
	},
	{
		String, Float,
		String,
	},
}

func TestTypeUnification(t *testing.T) {
	for _, tc := range typeUnificationTests {
		tc := tc
		t.Run(fmt.Sprintf("%s %s", tc.a, tc.b), func(t *testing.T) {
			t.Parallel()
			err := Unify(tc.a, tc.b)
			if err != nil {
				t.Errorf("%s", err)
			}
		})
	}
}

var groupOnlyMatchesTests = []struct {
	pattern  string
	check    string
	expected bool
}{
	{`\d+`,
		"0123456789",
		true,
	},
	{`[0123456789]`,
		"0123456789",
		true,
	},
	{`(0|1|2|3|4|5|6|7|8|9)`,
		"0123456789",
		true,
	},
	{`(\+|-)?\d+(\.\d+)?`,
		"0123456789",
		false,
	},
	{`(\d+\.\d+)`,
		"0123456789.eE+-",
		true,
	},
	{`(\+|-)?\d+(\.\d+)?`,
		"0123456789.eE+-",
		true,
	},
	{`(?P<offset>-?\d+\.\d+)`,
		"0123456789.eE+-",
		true,
	},
}

func TestGroupOnlyMatches(t *testing.T) {
	for _, tc := range groupOnlyMatchesTests {
		r, err := syntax.Parse(tc.pattern, syntax.Perl)
		if err != nil {
			t.Fatalf("syntax.Parse failed: %s", err)
		}
		result := groupOnlyMatches(r, tc.check)
		if result != tc.expected {
			t.Errorf("Pattern %q didn't only match check %q: expected %+v, received %+v", tc.pattern, tc.check, tc.expected, result)
		}
	}
}

var inferCaprefTypeTests = []struct {
	pattern string
	typ     Type
}{
	{`\d+`,
		Int,
	},
	{`[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?`,
		Float,
	},
	{`-?\d+\.\d+`,
		Float,
	},
	{`\d+\.\d+\.\d+\.\d+`,
		String,
	},
}

func TestInferCaprefType(t *testing.T) {
	for _, tc := range inferCaprefTypeTests {
		tc := tc
		t.Run(tc.pattern, func(t *testing.T) {
			t.Parallel()
			re, err := syntax.Parse(`(`+tc.pattern+`)`, syntax.Perl)
			if err != nil {
				t.Fatal(err)
			}
			r := inferCaprefType(re, 1)
			if !Equals(tc.typ, r) {
				t.Errorf("Types don't match: %q infers %v, not %v", tc.pattern, r, tc.typ)
			}
		})
	}
}

func TestTypeEquals(t *testing.T) {
	if Equals(NewTypeVariable(), NewTypeVariable()) {
		t.Error("Type variables are not same")
	}

	t1 := NewTypeVariable()
	t2 := NewTypeVariable()
	err := Unify(t1, t2)
	if err != nil {
		t.Fatal(err)
	}
	if !Equals(t1, t2) {
		t.Errorf("Unified variables should be same: %v %v", t1, t2)
	}
	if !Equals(Int, Int) {
		t.Errorf("type constants not same")
	}

	t3 := NewTypeVariable()
	if Equals(t3, Int) {
		t.Error("ununified type const and var")
	}
	err = Unify(Int, t3)

	if err != nil {
		t.Fatal(err)
	}
	if !Equals(t3, Int) {
		t.Error("unified variable and const not same")
	}
}
<commit_msg>Adds a test for the least upper bound of Pattern and Bool.<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
// This file is available under the Apache license.

package vm

import (
	"fmt"
	"regexp/syntax"
	"testing"
)

var typeUnificationTests = []struct {
	a, b     Type
	expected Type
}{
	// The unification of None with None is still None.
	{
		None, None,
		None,
	},
	// The unification of a type T with itself is T.
	{
		String, String,
		String,
	},
	{
		Int, Int,
		Int,
	},
	{
		Float, Float,
		Float,
	},
	{
		&TypeVariable{Id: 0}, &TypeVariable{Id: 0},
		&TypeVariable{Id: 0},
	},
	// The unification of any type operator with a type variable is the type operator
	{
		&TypeVariable{}, None,
		None,
	},
	{
		&TypeVariable{}, Float,
		Float,
	},
	{
		&TypeVariable{}, Int,
		Int,
	},
	{
		&TypeVariable{}, String,
		String,
	},
	{
		None, &TypeVariable{},
		None,
	},
	{
		Float, &TypeVariable{},
		Float,
	},
	{
		Int, &TypeVariable{},
		Int,
	},
	{
		String, &TypeVariable{},
		String,
	},
	// The lub of Int and Float is Float.
	{
		Int, Float,
		Float,
	},
	{
		Float, Int,
		Float,
	},
	// The lub of Int and String is String.
	{
		Int, String,
		String,
	},
	{
		String, Int,
		String,
	},
	// The lub of Float and String is String.
	{
		Float, String,
		String,
	},
	{
		String, Float,
		String,
	},
	// Implicitly, a Pattern by itself returns the value of its' match
	{
		Pattern, Bool,
		Bool,
	},
	{
		Bool, Pattern,
		Bool,
	},
}

func TestTypeUnification(t *testing.T) {
	for _, tc := range typeUnificationTests {
		tc := tc
		t.Run(fmt.Sprintf("%s %s", tc.a, tc.b), func(t *testing.T) {
			t.Parallel()
			err := Unify(tc.a, tc.b)
			if err != nil {
				t.Errorf("%s", err)
			}
		})
	}
}

var groupOnlyMatchesTests = []struct {
	pattern  string
	check    string
	expected bool
}{
	{`\d+`,
		"0123456789",
		true,
	},
	{`[0123456789]`,
		"0123456789",
		true,
	},
	{`(0|1|2|3|4|5|6|7|8|9)`,
		"0123456789",
		true,
	},
	{`(\+|-)?\d+(\.\d+)?`,
		"0123456789",
		false,
	},
	{`(\d+\.\d+)`,
		"0123456789.eE+-",
		true,
	},
	{`(\+|-)?\d+(\.\d+)?`,
		"0123456789.eE+-",
		true,
	},
	{`(?P<offset>-?\d+\.\d+)`,
		"0123456789.eE+-",
		true,
	},
}

func TestGroupOnlyMatches(t *testing.T) {
	for _, tc := range groupOnlyMatchesTests {
		r, err := syntax.Parse(tc.pattern, syntax.Perl)
		if err != nil {
			t.Fatalf("syntax.Parse failed: %s", err)
		}
		result := groupOnlyMatches(r, tc.check)
		if result != tc.expected {
			t.Errorf("Pattern %q didn't only match check %q: expected %+v, received %+v", tc.pattern, tc.check, tc.expected, result)
		}
	}
}

var inferCaprefTypeTests = []struct {
	pattern string
	typ     Type
}{
	{`\d+`,
		Int,
	},
	{`[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?`,
		Float,
	},
	{`-?\d+\.\d+`,
		Float,
	},
	{`\d+\.\d+\.\d+\.\d+`,
		String,
	},
}

func TestInferCaprefType(t *testing.T) {
	for _, tc := range inferCaprefTypeTests {
		tc := tc
		t.Run(tc.pattern, func(t *testing.T) {
			t.Parallel()
			re, err := syntax.Parse(`(`+tc.pattern+`)`, syntax.Perl)
			if err != nil {
				t.Fatal(err)
			}
			r := inferCaprefType(re, 1)
			if !Equals(tc.typ, r) {
				t.Errorf("Types don't match: %q infers %v, not %v", tc.pattern, r, tc.typ)
			}
		})
	}
}

func TestTypeEquals(t *testing.T) {
	if Equals(NewTypeVariable(), NewTypeVariable()) {
		t.Error("Type variables are not same")
	}

	t1 := NewTypeVariable()
	t2 := NewTypeVariable()
	err := Unify(t1, t2)
	if err != nil {
		t.Fatal(err)
	}
	if !Equals(t1, t2) {
		t.Errorf("Unified variables should be same: %v %v", t1, t2)
	}
	if !Equals(Int, Int) {
		t.Errorf("type constants not same")
	}

	t3 := NewTypeVariable()
	if Equals(t3, Int) {
		t.Error("ununified type const and var")
	}
	err = Unify(Int, t3)

	if err != nil {
		t.Fatal(err)
	}
	if !Equals(t3, Int) {
		t.Error("unified variable and const not same")
	}
}
<|endoftext|>


<commit_before>//  Copyright 2018 Google LLC
//  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

//        https://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 (
	"io/ioutil"
	"os"
	"testing"

	minions "github.com/google/minions/proto/minions"
	"github.com/stretchr/testify/require"
)

func TestParsesFiles_onFilesPresent_selectsFiles(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "/foo/bar", "temp.tmp", os.ModePerm)
	require.NoError(t, err)
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.tmp"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	p := files[0][0].GetMetadata().GetPath()
	require.Equal(t, dir+"/foo/bar/temp.tmp", p)
}

func TestParsesFiles_onSelectedFileUnaccessible_doesNotCrash(t *testing.T) {
	// We are owners of the file, but taking away our rights.
	dir, err := createFile(t, "common_goblins_test", "/foo/bar", "temp.tmp", os.ModePerm)
	require.NoError(t, err)
	err = os.Chmod(dir+"/foo/bar/temp.tmp", 00000)
	require.NoError(t, err)
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA_AND_DATA, PathRegexp: ".*\\.tmp"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	// Expect that will will skip, with no errors.
	require.Empty(t, files[0])
}

func TestParsesFiles_onMultipleInterests_selectsFiles(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "/foo/bar", "temp.tmp", os.ModePerm)
	require.NoError(t, err)
	defer os.RemoveAll(dir)
	fname := dir + "/foo/bar/temp.foo"
	_, err = os.Create(fname)
	require.NoError(t, err)
	err = os.Chmod(fname, os.ModePerm)
	require.NoError(t, err)

	i := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.tmp"}
	i2 := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.foo"}

	files, err := loadFiles([]*minions.Interest{i, i2}, 10000, 1000, dir)
	require.NoError(t, err)

	expectedFiles := []string{dir + "/foo/bar/temp.foo", dir + "/foo/bar/temp.tmp"}
	require.Contains(t, expectedFiles, files[0][0].GetMetadata().GetPath())
	require.Contains(t, expectedFiles, files[0][1].GetMetadata().GetPath())
}

func TestParsesFiles_anchorsRegexpToRoot(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "/different/foo/bar", "temp.tmp", os.ModePerm)
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: "/foo/bar"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	// Note that empty does not check recursively, so we fetch the first (empty) element.
	require.Empty(t, files[0])
}

func TestParsesFiles_onFilesPresent_getsMetadata(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "/foo", "temp.tmp", 0700)
	require.NoError(t, err)
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.tmp"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	p := files[0][0].GetMetadata().GetPermissions()

	require.Equal(t, uint32(0700), p)
}

func TestParsesFiles_onFilesPresent_readsContents(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "", "data.tmp", 0700)
	b := []byte{115, 111, 109, 101, 10}
	f, err := os.Create(dir + "/data.tmp")
	_, err = f.Write(b)
	require.NoError(t, err)
	f.Close()
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA_AND_DATA, PathRegexp: ".*\\.tmp"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	chunks := files[0][0].GetDataChunks()
	data := chunks[0].GetData()
	require.Equal(t, b, data)
}

func TestParsesFiles_onFilesMissing_doesNotSelectFiles(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "/", "temp.val1", os.ModePerm)
	require.NoError(t, err)
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.val2"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	// Sadly empty does not really support 2 dimensional slices.
	require.Empty(t, files[0])
}

func createFile(t *testing.T, base string, subdirs string, name string, perm os.FileMode) (string, error) {
	dir, err := ioutil.TempDir("", base)
	err = os.MkdirAll(dir+subdirs, perm)
	require.NoError(t, err)
	fname := dir + subdirs + "/" + name
	_, err = os.Create(fname)
	require.NoError(t, err)
	err = os.Chmod(fname, perm)
	require.NoError(t, err)
	return dir, err
}
<commit_msg>Remove tests for a removed behavior<commit_after>//  Copyright 2018 Google LLC
//  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

//        https://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 (
	"io/ioutil"
	"os"
	"testing"

	minions "github.com/google/minions/proto/minions"
	"github.com/stretchr/testify/require"
)

func TestParsesFiles_onFilesPresent_selectsFiles(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "/foo/bar", "temp.tmp", os.ModePerm)
	require.NoError(t, err)
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.tmp"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	p := files[0][0].GetMetadata().GetPath()
	require.Equal(t, dir+"/foo/bar/temp.tmp", p)
}

func TestParsesFiles_onSelectedFileUnaccessible_doesNotCrash(t *testing.T) {
	// We are owners of the file, but taking away our rights.
	dir, err := createFile(t, "common_goblins_test", "/foo/bar", "temp.tmp", os.ModePerm)
	require.NoError(t, err)
	err = os.Chmod(dir+"/foo/bar/temp.tmp", 00000)
	require.NoError(t, err)
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA_AND_DATA, PathRegexp: ".*\\.tmp"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	// Expect that will will skip, with no errors.
	require.Empty(t, files[0])
}

func TestParsesFiles_onMultipleInterests_selectsFiles(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "/foo/bar", "temp.tmp", os.ModePerm)
	require.NoError(t, err)
	defer os.RemoveAll(dir)
	fname := dir + "/foo/bar/temp.foo"
	_, err = os.Create(fname)
	require.NoError(t, err)
	err = os.Chmod(fname, os.ModePerm)
	require.NoError(t, err)

	i := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.tmp"}
	i2 := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.foo"}

	files, err := loadFiles([]*minions.Interest{i, i2}, 10000, 1000, dir)
	require.NoError(t, err)

	expectedFiles := []string{dir + "/foo/bar/temp.foo", dir + "/foo/bar/temp.tmp"}
	require.Contains(t, expectedFiles, files[0][0].GetMetadata().GetPath())
	require.Contains(t, expectedFiles, files[0][1].GetMetadata().GetPath())
}

func TestParsesFiles_onFilesPresent_getsMetadata(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "/foo", "temp.tmp", 0700)
	require.NoError(t, err)
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.tmp"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	p := files[0][0].GetMetadata().GetPermissions()

	require.Equal(t, uint32(0700), p)
}

func TestParsesFiles_onFilesPresent_readsContents(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "", "data.tmp", 0700)
	b := []byte{115, 111, 109, 101, 10}
	f, err := os.Create(dir + "/data.tmp")
	_, err = f.Write(b)
	require.NoError(t, err)
	f.Close()
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA_AND_DATA, PathRegexp: ".*\\.tmp"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	chunks := files[0][0].GetDataChunks()
	data := chunks[0].GetData()
	require.Equal(t, b, data)
}

func TestParsesFiles_onFilesMissing_doesNotSelectFiles(t *testing.T) {
	dir, err := createFile(t, "common_goblins_test", "/", "temp.val1", os.ModePerm)
	require.NoError(t, err)
	defer os.RemoveAll(dir)

	i := &minions.Interest{DataType: minions.Interest_METADATA, PathRegexp: ".*\\.val2"}
	files, err := loadFiles([]*minions.Interest{i}, 10000, 1000, dir)
	require.NoError(t, err)
	// Sadly empty does not really support 2 dimensional slices.
	require.Empty(t, files[0])
}

func createFile(t *testing.T, base string, subdirs string, name string, perm os.FileMode) (string, error) {
	dir, err := ioutil.TempDir("", base)
	err = os.MkdirAll(dir+subdirs, perm)
	require.NoError(t, err)
	fname := dir + subdirs + "/" + name
	_, err = os.Create(fname)
	require.NoError(t, err)
	err = os.Chmod(fname, perm)
	require.NoError(t, err)
	return dir, err
}
<|endoftext|>


<commit_before>// Copyright 2015-2019 Bret Jordan, All rights reserved.
//
// Use of this source code is governed by an Apache 2.0 license that can be
// found in the LICENSE file in the root of the source tree.

package vocabs

// AttackMotivation - This defines the STIX attack motivation vocabulary.
var AttackMotivation = []string{
	"accidental",
	"coercion",
	"dominance",
	"ideology",
	"notoriety",
	"organizational-gain",
	"personal-gain",
	"personal-satisfaction",
	"revenge",
	"unpredictable",
}

// AttackResourceLevel - This defines the STIX attack resource level vocabulary.
var AttackResourceLevel = []string{
	"individual",
	"club",
	"content",
	"team",
	"organization",
	"government",
}

// IdentityClass - This defines the STIX identity class vocabulary.
var IdentityClass = []string{
	"individual",
	"group",
	"organization",
	"class",
	"unknown",
}

// IndicatorLabel - This defines the STIX indicator label vocabulary.
var IndicatorLabel = []string{
	"anomalous-activity",
	"anonymization",
	"benign",
	"compromised",
	"malicious-activity",
	"attribution",
}

// IndustrySector - This defines the STIX industry sector vocabulary.
var IndustrySector = []string{
	"agriculture",
	"aerospace",
	"automotive",
	"communications",
	"construction",
	"defense",
	"education",
	"energy",
	"entertainment",
	"financial-services",
	"government-national",
	"government-regional",
	"government-local",
	"government-public-services",
	"healthcare",
	"hospitality-leisure",
	"infrastructure",
	"insurance",
	"manufacturing",
	"mining",
	"non-profit",
	"pharmaceuticals",
	"retail",
	"technology",
	"telecommunications",
	"transportation",
	"utilities",
}

// MalwareLabel - This defines the STIX malware label vocabulary.
var MalwareLabel = []string{
	"adware",
	"backdoor",
	"bot",
	"ddos",
	"dropper",
	"exploit-kit",
	"keylogger",
	"ransomware",
	"remote-access-trojan",
	"resource-exploitation",
	"rogue-security-software",
	"rootkit",
	"screen-capture",
	"spyware",
	"trojan",
	"virus",
	"worm",
}

// MalwareTypes - This defines the STIX malware types vocabulary.
var MalwareTypes = []string{
	"adware",
	"backdoor",
	"bot",
	"bootkit",
	"ddos",
	"downloader",
	"dropper",
	"exploit-kit",
	"keylogger",
	"ransomware",
	"remote-access-trojan",
	"resource-exploitation",
	"rogue-security-software",
	"rootkit",
	"screen-capture",
	"spyware",
	"trojan",
	"unknown",
	"virus",
	"webshell",
	"wiper",
	"worm",
}

// ArchitectureExecutionEnvs This defines the STIX malware architecture execution envs vocabulary.
var ArchitectureExecutionEnvs = []string{
	"alpha",
	"arm",
	"ia-64",
	"mips",
	"powerpc",
	"sparc",
	"x86",
	"x86-64",
}

// ImplementationLanguages This defines the STIX malware implementation languages vocabulary.
var ImplementationLanguages = []string{
	"applescript",
	"bash",
	"c",
	"c++",
	"c#",
	"go",
	"java",
	"javascript",
	"lua",
	"objective-c",
	"perl",
	"php",
	"powershell",
	"python",
	"ruby",
	"scala",
	"swift",
	"typescript",
	"visual-basic",
	"x86-32",
	"x86-64",
}

// MalwareCapabilities This defines the STIX malware malware capabilities vocabulary.
var MalwareCapabilities = []string{
	"accesses-remote-machines",
	"anti-debugging",
	"anti-disassembly",
	"anti-emulation",
	"anti-memory-forensics",
	"anti-sandbox",
	"anti-vm",
	"captures-input-peripherals",
	"captures-output-peripherals",
	"captures-system-state-data",
	"cleans-traces-of-infection",
	"commits-fraud",
	"communicates-with-c2",
	"compromises-data-availability",
	"compromises-data-integrity",
	"compromises-system-availability",
	"controls-local-machine",
	"degrades-security-software",
	"degrades-system-updates",
	"determines-c2-server",
	"emails-spam",
	"escalates-privileges",
	"evades-av",
	"exfiltrates-data",
	"fingerprints-host",
	"hides-artifacts",
	"hides-executing-code",
	"infects-files",
	"infects-remote-machines",
	"installs-other-components",
	"persists-after-system-reboot",
	"prevents-artifact-access",
	"prevents-artifact-deletion",
	"probes-network-environment",
	"self-modifies",
	"steals-authentication-credentials",
	"violates-system-operational-integrity",
}

// InfrastructureTypes
var InfrastructureTypes = []string{
	"amplification",
	"anonymization",
	"botnet",
	"command-and-control",
	"exfiltration",
	"hosting-malware",
	"hosting-target-lists",
	"phishing",
	"reconnaissance",
	"staging",
	"undefined",
}

// ReportLabel - This defines the STIX report label vocabulary.
var ReportLabel = []string{
	"threat-report",
	"attack-pattern",
	"campaign",
	"indicator",
	"malware",
	"observed-data",
	"threat-actor",
	"tool",
	"victim-target",
	"vulnerability",
}

// ThreatActorLabel - This defines the STIX threat actor label vocabulary.
var ThreatActorLabel = []string{
	"activist",
	"competitor",
	"crime-syndicate",
	"criminal",
	"hacker",
	"insider-accidental",
	"insider-disgruntled",
	"nation-state",
	"sensationalist",
	"spy",
	"terrorist",
}

// ThreatActorRole - This defines the STIX threat actor role vocabulary.
var ThreatActorRole = []string{
	"agent",
	"director",
	"independent",
	"infrastructure-architect",
	"infrastructure-operator",
	"malware-author",
	"sponsor",
}

// ThreatActorSophistication - This defines the STIX threat actor sophistication vocabulary.
var ThreatActorSophistication = []string{
	"none",
	"minimal",
	"intermediate",
	"advanced",
	"expert",
	"innovator",
	"strategic",
}

// ToolLabel - This defines the STIX tool label vocabulary.
var ToolLabel = []string{
	"denial-of-service",
	"exploitation",
	"information-gathering",
	"network-capture",
	"credential-exploitation",
	"remote-access",
	"vulnerability-scanning",
}
<commit_msg>update to 2.1<commit_after>// Copyright 2015-2019 Bret Jordan, All rights reserved.
//
// Use of this source code is governed by an Apache 2.0 license that can be
// found in the LICENSE file in the root of the source tree.

package vocabs

// Account - This defines the STIX account vocabulary.
var Account = []string{
	"facebook",
	"ldap",
	"nis",
	"openid",
	"radius",
	"skype",
	"tacacs",
	"twitter",
	"unix",
	"windows-local",
	"windows-domain",
}

// AttackMotivation - This defines the STIX attack motivation vocabulary.
var AttackMotivation = []string{
	"accidental",
	"coercion",
	"dominance",
	"ideology",
	"notoriety",
	"organizational-gain",
	"personal-gain",
	"personal-satisfaction",
	"revenge",
	"unpredictable",
}

// AttackResourceLevel - This defines the STIX attack resource level vocabulary.
var AttackResourceLevel = []string{
	"individual",
	"club",
	"contest",
	"team",
	"organization",
	"government",
}

// CourseOfAction - This defines the STIX course of action vocabulary.
var CourseOfAction = []string{
	"textual:text/plain",
	"textual:text/html",
	"textual:text/md",
	"textual:pdf",
}

// Encryption - This defines the STIX encryption enumeration.
var Encryption = []string{
	"AES-256-GCM",
	"ChaCha20-Poly1305",
	"mime-type-indicated",
}

// Grouping - This defines the STIX grouping vocabulary.
var Grouping = []string{
	"suspicious-activity",
	"malware-analysis",
	"unspecified",
}

// HashingAlgorithm - This defines the STIX hashing algorithm vocabulary.
var HashingAlgorithm = []string{
	"MD5",
	"SHA-1",
	"SHA-256",
	"SHA-512",
	"SHA3-256",
	"SHA3-512",
	"SSDEEP",
	"TLSH",
}

// IdentityClass - This defines the STIX identity class vocabulary.
var IdentityClass = []string{
	"individual",
	"group",
	"system",
	"organization",
	"class",
	"unspecified",
}

// IndicatorLabel - This defines the STIX indicator label vocabulary.
var IndicatorLabel = []string{
	"anomalous-activity",
	"anonymization",
	"benign",
	"compromised",
	"malicious-activity",
	"attribution",
}

// ImplementationLanguage - This defines the STIX implementation language vocabulary.
var ImplementationLanguage = []string{
	"applescript",
	"bash",
	"c",
	"c++",
	"c#",
	"go",
	"java",
	"javascript",
	"lua",
	"objective-c",
	"perl",
	"php",
	"powershell",
	"python",
	"ruby",
	"scala",
	"swift",
	"typescript",
	"visual-basic",
	"x86-32",
	"x86-64",
}

// IndicatorType - This defines the STIX indicator type vocabulary.
var IndicatorType = []string{
	"anomalous-activity",
	"anonymization",
	"benign",
	"compromised",
	"malicious-activity",
	"attribution",
	"unknown",
}

// IndustrySector - This defines the STIX industry sector vocabulary.
var IndustrySector = []string{
	"agriculture",
	"aerospace",
	"automotive",
	"communications",
	"construction",
	"defense",
	"education",
	"energy",
	"entertainment",
	"financial-services",
	"government-national",
	"government-regional",
	"government-local",
	"government-public-services",
	"healthcare",
	"hospitality-leisure",
	"infrastructure",
	"insurance",
	"manufacturing",
	"mining",
	"non-profit",
	"pharmaceuticals",
	"retail",
	"technology",
	"telecommunications",
	"transportation",
	"utilities",
}

// InfrastructureType - This defines the STIX infrastructure type vocabulary.
var InfrastructureType = []string{
	"amplification",
	"anonymization",
	"botnet",
	"command-and-control",
	"exfiltration",
	"hosting-malware",
	"hosting-target-lists",
	"phishing",
	"reconnaissance",
	"staging",
	"undefined",
}

// MalwareAVResults - This defines the STIX malware av results vocabulary.
var MalwareAVResults = []string{
	"malicious",
	"suspicious",
	"benign",
	"unknown",
}

// MalwareCapabilities - This defines the STIX malware capabilities vocabulary.
var MalwareCapabilities = []string{
	"accesses-remote-machines",
	"anti-debugging",
	"anti-disassembly",
	"anti-emulation",
	"anti-memory-forensics",
	"anti-sandbox",
	"anti-vm",
	"captures-input-peripherals",
	"captures-output-peripherals",
	"captures-system-state-data",
	"cleans-traces-of-infection",
	"commits-fraud",
	"communicates-with-c2",
	"compromises-data-availability",
	"compromises-data-integrity",
	"compromises-system-availability",
	"controls-local-machine",
	"degrades-security-software",
	"degrades-system-updates",
	"determines-c2-server",
	"emails-spam",
	"escalates-privileges",
	"evades-av",
	"exfiltrates-data",
	"fingerprints-host",
	"hides-artifacts",
	"hides-executing-code",
	"infects-files",
	"infects-remote-machines",
	"installs-other-components",
	"persists-after-system-reboot",
	"prevents-artifact-access",
	"prevents-artifact-deletion",
	"probes-network-environment",
	"self-modifies",
	"steals-authentication-credentials",
	"violates-system-operational-integrity",
}

// MalwareType - This defines the STIX malware type vocabulary.
var MalwareType = []string{
	"adware",
	"backdoor",
	"bot",
	"bootkit",
	"ddos",
	"downloader",
	"dropper",
	"exploit-kit",
	"keylogger",
	"ransomware",
	"remote-access-trojan",
	"resource-exploitation",
	"rogue-security-software",
	"rootkit",
	"screen-capture",
	"spyware",
	"trojan",
	"unknown",
	"virus",
	"webshell",
	"wiper",
	"worm",
}

// NetworkSocketAddressFamily - This defines the STIX network socket address family enumeration.
var NetworkSocketAddressFamily = []string{
	"AF_UNSPEC",
	"AF_INET",
	"AF_IPX",
	"AF_APPLETALK",
	"AF_NETBIOS",
	"AF_INET6",
	"AF_IRDA",
	"AF_BTH",
}

// NetworkSocketType - This defines the STIX network socket type enumeration.
var NetworkSocketType = []string{
	"SOCK_STREAM",
	"AF_ISOCK_DGRAMNET",
	"SOCK_RAW, SOCK_RDM",
	"SOCK_SEQPACKET",
}

// Opinion - This defines the STIX opinion enumeration.
var Opinion = []string{
	"strongly-disagree",
	"disagree",
	"neutral",
	"agree",
	"strongly-agree",
}

// PatternType - This defines the STIX pattern type vocabulary.
var PatternType = []string{
	"stix",
	"pcre",
	"sigma",
	"snort",
	"suricata",
	"yara",
}

// ProcessorArchitecture - This defines the STIX processor architecture vocabulary.
var ProcessorArchitecture = []string{
	"alpha",
	"arm",
	"ia-64",
	"mips",
	"powerpc",
	"sparc",
	"x86",
	"x86-64",
}

// Region - This defines the STIX region vocabulary.
var Region = []string{
	"africa",
	"eastern-africa",
	"middle-africa",
	"northern-africa",
	"southern-africa",
	"western-africa",
	"americas",
	"latin-america-caribbean",
	"south-america",
	"caribbean",
	"central-america",
	"northern-america",
	"asia",
	"central-asia",
	"eastern-asia",
	"southern-asia",
	"south-eastern-asia",
	"western-asia",
	"europe",
	"eastern-europe",
	"northern-europe",
	"southern-europe",
	"western-europe",
	"oceania",
	"antarctica",
	"australia-new-zealand",
	"melanesia",
	"micronesia",
	"polynesia",
}

// ReportType - This defines the STIX report type vocabulary.
var ReportType = []string{
	"attack-pattern",
	"campaign",
	"identity",
	"indicator",
	"intrusion-set",
	"malware",
	"observed-data",
	"threat-actor",
	"threat-report",
	"tool",
	"vulnerability",
}

// ThreatActorType - This defines the STIX threat actor type vocabulary.
var ThreatActorType = []string{
	"activist",
	"competitor",
	"crime-syndicate",
	"criminal",
	"hacker",
	"insider-accidental",
	"insider-disgruntled",
	"nation-state",
	"sensationalist",
	"spy",
	"terrorist",
	"unknown",
}

// ThreatActorRole - This defines the STIX threat actor role vocabulary.
var ThreatActorRole = []string{
	"agent",
	"director",
	"independent",
	"infrastructure-architect",
	"infrastructure-operator",
	"malware-author",
	"sponsor",
}

// ThreatActorSophistication - This defines the STIX threat actor sophistication vocabulary.
var ThreatActorSophistication = []string{
	"none",
	"minimal",
	"intermediate",
	"advanced",
	"expert",
	"innovator",
	"strategic",
}

// ToolType - This defines the STIX tool type vocabulary.
var ToolType = []string{
	"denial-of-service",
	"exploitation",
	"information-gathering",
	"network-capture",
	"credential-exploitation",
	"remote-access",
	"vulnerability-scanning",
	"unknown",
}

// WindowsIntegrityLevel - This defines the STIX Windows integrity level enumeration
var WindowsIntegrityLevel = []string{
	"low",
	"medium",
	"high",
	"system",
}

// WindowsPEBinary - This defines the STIX Windows pe binary vocabulary
var WindowsPEBinary = []string{
	"dll",
	"exe",
	"sys",
}

// WindowsRegistryDatatype - This defines the STIX Windows registry datatype enumeration.
var WindowsRegistryDatatype = []string{
	"REG_NONE",
	"REG_SZ",
	"REG_EXPAND_SZ",
	"REG_BINARY",
	"REG_DWORD",
	"REG_DWORD_BIG_ENDIAN",
	"REG_DWORD_LITTLE_ENDIAN",
	"REG_LINK",
	"REG_MULTI_SZ",
	"REG_RESOURCE_LIST",
	"REG_FULL_RESOURCE_DESCRIPTION",
	"REG_RESOURCE_REQUIREMENTS_LIST",
	"REG_QWORD",
	"REG_INVALID_TYPE",
}

// WindowsServiceStartType - This defines the STIX Windows service start type enumeration.
var WindowsServiceStartType = []string{
	"SERVICE_AUTO_START",
	"SERVICE_BOOT_START",
	"SERVICE_DEMAND_START",
	"SERVICE_DISABLED",
	"SERVICE_SYSTEM_ALERT",
}

// WindowsServiceType - This defines the STIX Windows service type enumeration.
var WindowsServiceType = []string{
	"SERVICE_KERNEL_DRIVER",
	"SERVICE_FILE_SYSTEM_DRIVER",
	"SERVICE_WIN32_OWN_PROCESS",
	"SERVICE_WIN32_SHARE_PROCESS",
}

// WindowsServiceStatus - This defines the STIX Windows service status enumeration.
var WindowsServiceStatus = []string{
	"SERVICE_CONTINUE_PENDING",
	"SERVICE_PAUSE_PENDING",
	"SERVICE_PAUSED",
	"SERVICE_RUNNING",
	"SERVICE_START_PENDING",
	"SERVICE_STOP_PENDING",
	"SERVICE_STOPPED",
}
<|endoftext|>


<commit_before>// +build windows

package wincred

import (
	"errors"
	"testing"
	"unsafe"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type mockProc struct {
	mock.Mock
	orig   proc
	target *proc
}

func (t *mockProc) Setup(target *proc) {
	t.target = target
	t.orig = *t.target
	*(t.target) = t
}

func (t *mockProc) TearDown() {
	*(t.target) = t.orig
}

func (t *mockProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
	args := t.Called(a)
	return uintptr(args.Int(0)), uintptr(args.Int(1)), args.Error(2)
}

func TestSysCredRead_MockFailure(t *testing.T) {
	// The test error
	testError := errors.New("test error")
	// Mock `CreadRead`: returns failure state and the error
	mockCredRead := new(mockProc)
	mockCredRead.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, testError)
	mockCredRead.Setup(&procCredRead)
	defer mockCredRead.TearDown()
	// Mock `CredFree`: Must not be called
	mockCredFree := new(mockProc)
	mockCredFree.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, nil)
	mockCredFree.Setup(&procCredFree)
	defer mockCredFree.TearDown()

	// Test it:
	var res *Credential
	var err error
	assert.NotPanics(t, func() { res, err = sysCredRead("foo", sysCRED_TYPE_GENERIC) })
	assert.Nil(t, res)
	assert.NotNil(t, err)
	assert.Equal(t, "test error", err.Error())
	mockCredRead.AssertNumberOfCalls(t, "Call", 1)
	mockCredFree.AssertNumberOfCalls(t, "Call", 0)
}

func TestSysCredRead_Mock(t *testing.T) {
	// prepare some test data
	cred := new(Credential)
	cred.TargetName = "Foo"
	cred.Comment = "Bar"
	cred.CredentialBlob = []byte{1, 2, 3}
	credSys := sysFromCredential(cred)
	// Mock `CreadRead`: returns success and sets the pointer to the prepared sysCred struct
	mockCredRead := new(mockProc)
	mockCredRead.
		On("Call", mock.AnythingOfType("[]uintptr")).
		Return(1, 0, nil).
		Run(func(args mock.Arguments) {
			arg := args.Get(0).([]uintptr)
			assert.Equal(t, 4, len(arg))
			*(*uintptr)(unsafe.Pointer(arg[3])) = uintptr(unsafe.Pointer(credSys))
		})
	mockCredRead.Setup(&procCredRead)
	defer mockCredRead.TearDown()
	// Mock `CredFree`: Must be called as well with the correct pointer
	mockCredFree := new(mockProc)
	mockCredFree.
		On("Call", mock.AnythingOfType("[]uintptr")).
		Return(0, 0, nil).
		Run(func(args mock.Arguments) {
			arg := args.Get(0).([]uintptr)
			assert.Equal(t, 1, len(arg))
			assert.Equal(t, uintptr(unsafe.Pointer(credSys)), arg[0])
		})
	mockCredFree.Setup(&procCredFree)
	defer mockCredFree.TearDown()

	// Test it:
	var res *Credential
	var err error
	assert.NotPanics(t, func() { res, err = sysCredRead("Foo", sysCRED_TYPE_GENERIC) })
	mockCredRead.AssertNumberOfCalls(t, "Call", 1)
	mockCredFree.AssertNumberOfCalls(t, "Call", 1)
	assert.NotNil(t, res)
	assert.Nil(t, err)
	assert.Equal(t, "Foo", res.TargetName)
	assert.Equal(t, "Bar", res.Comment)
	assert.Equal(t, []byte{1, 2, 3}, res.CredentialBlob)
	assert.NotEqual(t, &cred, &res)
}

func TestSysCredWrite_MockFailure(t *testing.T) {
	// Mock `CreadWrite`: returns failure state and the error
	mockCredWrite := new(mockProc)
	mockCredWrite.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, errors.New("test error"))
	mockCredWrite.Setup(&procCredWrite)
	defer mockCredWrite.TearDown()

	// Test it:
	var err error
	assert.NotPanics(t, func() { err = sysCredWrite(new(Credential), sysCRED_TYPE_GENERIC) })
	assert.NotNil(t, err)
	assert.Equal(t, "test error", err.Error())
	mockCredWrite.AssertNumberOfCalls(t, "Call", 1)
}

func TestSysCredWrite_Mock(t *testing.T) {
	// Mock `CreadWrite`: returns success state
	mockCredWrite := new(mockProc)
	mockCredWrite.On("Call", mock.AnythingOfType("[]uintptr")).Return(1, 0, nil)
	mockCredWrite.Setup(&procCredWrite)
	defer mockCredWrite.TearDown()

	// Test it:
	var err error
	assert.NotPanics(t, func() { err = sysCredWrite(new(Credential), sysCRED_TYPE_GENERIC) })
	assert.Nil(t, err)
	mockCredWrite.AssertNumberOfCalls(t, "Call", 1)
}

func TestSysCredDelete_MockFailure(t *testing.T) {
	// Mock `CreadDelete`: returns failure state and an error
	mockCredDelete := new(mockProc)
	mockCredDelete.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, errors.New("test error"))
	mockCredDelete.Setup(&procCredDelete)
	defer mockCredDelete.TearDown()

	// Test it:
	var err error
	assert.NotPanics(t, func() { err = sysCredDelete(new(Credential), sysCRED_TYPE_GENERIC) })
	assert.NotNil(t, err)
	assert.Equal(t, "test error", err.Error())
	mockCredDelete.AssertNumberOfCalls(t, "Call", 1)
}

func TestSysCredDelete_Mock(t *testing.T) {
	// Mock `CreadDelete`: returns success state
	mockCredDelete := new(mockProc)
	mockCredDelete.On("Call", mock.AnythingOfType("[]uintptr")).Return(1, 0, nil)
	mockCredDelete.Setup(&procCredDelete)
	defer mockCredDelete.TearDown()

	// Test it:
	var err error
	assert.NotPanics(t, func() { err = sysCredDelete(new(Credential), sysCRED_TYPE_GENERIC) })
	assert.Nil(t, err)
	mockCredDelete.AssertNumberOfCalls(t, "Call", 1)
}

func TestSysCredEnumerate_MockFailure(t *testing.T) {
	// The test error
	testError := errors.New("test error")
	// Mock `CreadEnumerate`: returns failure state and the error
	mockCredEnumerate := new(mockProc)
	mockCredEnumerate.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, testError)
	mockCredEnumerate.Setup(&procCredEnumerate)
	defer mockCredEnumerate.TearDown()
	// Mock `CredFree`: Must not be called
	mockCredFree := new(mockProc)
	mockCredFree.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, nil)
	mockCredFree.Setup(&procCredFree)
	defer mockCredFree.TearDown()

	// Test it:
	var res []*Credential
	var err error
	assert.NotPanics(t, func() { res, err = sysCredEnumerate("", true) })
	assert.Nil(t, res)
	assert.NotNil(t, err)
	assert.Equal(t, "test error", err.Error())
	mockCredEnumerate.AssertNumberOfCalls(t, "Call", 1)
	mockCredFree.AssertNumberOfCalls(t, "Call", 0)
}

func TestSysCredEnumerate_Mock(t *testing.T) {
	// prepare some test data
	creds := []*Credential{new(Credential), new(Credential)}
	creds[0].TargetName = "Foo"
	creds[1].TargetName = "Bar"
	credsSys := [](*sysCREDENTIAL){
		sysFromCredential(creds[0]),
		sysFromCredential(creds[1]),
	}

	// Mock `CreadEnumerate`: returns success and sets the pointer to the prepared sysCreds array
	mockCredEnumerate := new(mockProc)
	mockCredEnumerate.
		On("Call", mock.AnythingOfType("[]uintptr")).
		Return(1, 0, nil).
		Run(func(args mock.Arguments) {
			arg := args.Get(0).([]uintptr)
			assert.Equal(t, 4, len(arg))
			*(*int)(unsafe.Pointer(arg[2])) = len(credsSys)
			*(*uintptr)(unsafe.Pointer(arg[3])) = uintptr(unsafe.Pointer(&credsSys[0]))
		})
	mockCredEnumerate.Setup(&procCredEnumerate)
	defer mockCredEnumerate.TearDown()
	// Mock `CredFree`: Must be called as well with the correct pointer
	mockCredFree := new(mockProc)
	mockCredFree.
		On("Call", mock.AnythingOfType("[]uintptr")).
		Return(0, 0, nil).
		Run(func(args mock.Arguments) {
			arg := args.Get(0).([]uintptr)
			assert.Equal(t, 1, len(arg))
			assert.Equal(t, uintptr(unsafe.Pointer(&credsSys[0])), arg[0])
		})
	mockCredFree.Setup(&procCredFree)
	defer mockCredFree.TearDown()

	// Test it:
	var res []*Credential
	var err error
	assert.NotPanics(t, func() { res, err = sysCredEnumerate("", true) })
	mockCredEnumerate.AssertNumberOfCalls(t, "Call", 1)
	mockCredFree.AssertNumberOfCalls(t, "Call", 1)
	assert.NotNil(t, res)
	assert.Nil(t, err)
	assert.Equal(t, 2, len(res))
	assert.Equal(t, "Foo", res[0].TargetName)
	assert.Equal(t, "Bar", res[1].TargetName)
}
<commit_msg>Adds workaround to sys_test.go to keep objects alive<commit_after>// +build windows

package wincred

import (
	"errors"
	"testing"
	"unsafe"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type mockProc struct {
	mock.Mock
	orig   proc
	target *proc
}

func (t *mockProc) Setup(target *proc) {
	t.target = target
	t.orig = *t.target
	*(t.target) = t
}

func (t *mockProc) TearDown() {
	*(t.target) = t.orig
}

func (t *mockProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
	args := t.Called(a)
	return uintptr(args.Int(0)), uintptr(args.Int(1)), args.Error(2)
}

func TestSysCredRead_MockFailure(t *testing.T) {
	// The test error
	testError := errors.New("test error")
	// Mock `CreadRead`: returns failure state and the error
	mockCredRead := new(mockProc)
	mockCredRead.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, testError)
	mockCredRead.Setup(&procCredRead)
	defer mockCredRead.TearDown()
	// Mock `CredFree`: Must not be called
	mockCredFree := new(mockProc)
	mockCredFree.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, nil)
	mockCredFree.Setup(&procCredFree)
	defer mockCredFree.TearDown()

	// Test it:
	var res *Credential
	var err error
	assert.NotPanics(t, func() { res, err = sysCredRead("foo", sysCRED_TYPE_GENERIC) })
	assert.Nil(t, res)
	assert.NotNil(t, err)
	assert.Equal(t, "test error", err.Error())
	mockCredRead.AssertNumberOfCalls(t, "Call", 1)
	mockCredFree.AssertNumberOfCalls(t, "Call", 0)
}

func TestSysCredRead_Mock(t *testing.T) {
	// prepare some test data
	cred := new(Credential)
	cred.TargetName = "Foo"
	cred.Comment = "Bar"
	cred.CredentialBlob = []byte{1, 2, 3}
	credSys := sysFromCredential(cred)
	t.Log(credSys) // Workaround to keep the object alive

	// Mock `CreadRead`: returns success and sets the pointer to the prepared sysCred struct
	mockCredRead := new(mockProc)
	mockCredRead.
		On("Call", mock.AnythingOfType("[]uintptr")).
		Return(1, 0, nil).
		Run(func(args mock.Arguments) {
			arg := args.Get(0).([]uintptr)
			assert.Equal(t, 4, len(arg))
			*(**sysCREDENTIAL)(unsafe.Pointer(arg[3])) = credSys
		})
	mockCredRead.Setup(&procCredRead)
	defer mockCredRead.TearDown()

	// Mock `CredFree`: Must be called as well with the correct pointer
	mockCredFree := new(mockProc)
	mockCredFree.
		On("Call", mock.AnythingOfType("[]uintptr")).
		Return(0, 0, nil).
		Run(func(args mock.Arguments) {
			arg := args.Get(0).([]uintptr)
			assert.Equal(t, 1, len(arg))
			assert.Equal(t, uintptr(unsafe.Pointer(credSys)), arg[0])
		})
	mockCredFree.Setup(&procCredFree)
	defer mockCredFree.TearDown()

	// Test it:
	var res *Credential
	var err error
	assert.NotPanics(t, func() { res, err = sysCredRead("Foo", sysCRED_TYPE_GENERIC) })
	mockCredRead.AssertNumberOfCalls(t, "Call", 1)
	mockCredFree.AssertNumberOfCalls(t, "Call", 1)
	assert.NotNil(t, res)
	assert.Nil(t, err)
	assert.Equal(t, "Foo", res.TargetName)
	assert.Equal(t, "Bar", res.Comment)
	assert.Equal(t, []byte{1, 2, 3}, res.CredentialBlob)
	assert.NotEqual(t, &cred, &res)
}

func TestSysCredWrite_MockFailure(t *testing.T) {
	// Mock `CreadWrite`: returns failure state and the error
	mockCredWrite := new(mockProc)
	mockCredWrite.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, errors.New("test error"))
	mockCredWrite.Setup(&procCredWrite)
	defer mockCredWrite.TearDown()

	// Test it:
	var err error
	assert.NotPanics(t, func() { err = sysCredWrite(new(Credential), sysCRED_TYPE_GENERIC) })
	assert.NotNil(t, err)
	assert.Equal(t, "test error", err.Error())
	mockCredWrite.AssertNumberOfCalls(t, "Call", 1)
}

func TestSysCredWrite_Mock(t *testing.T) {
	// Mock `CreadWrite`: returns success state
	mockCredWrite := new(mockProc)
	mockCredWrite.On("Call", mock.AnythingOfType("[]uintptr")).Return(1, 0, nil)
	mockCredWrite.Setup(&procCredWrite)
	defer mockCredWrite.TearDown()

	// Test it:
	var err error
	assert.NotPanics(t, func() { err = sysCredWrite(new(Credential), sysCRED_TYPE_GENERIC) })
	assert.Nil(t, err)
	mockCredWrite.AssertNumberOfCalls(t, "Call", 1)
}

func TestSysCredDelete_MockFailure(t *testing.T) {
	// Mock `CreadDelete`: returns failure state and an error
	mockCredDelete := new(mockProc)
	mockCredDelete.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, errors.New("test error"))
	mockCredDelete.Setup(&procCredDelete)
	defer mockCredDelete.TearDown()

	// Test it:
	var err error
	assert.NotPanics(t, func() { err = sysCredDelete(new(Credential), sysCRED_TYPE_GENERIC) })
	assert.NotNil(t, err)
	assert.Equal(t, "test error", err.Error())
	mockCredDelete.AssertNumberOfCalls(t, "Call", 1)
}

func TestSysCredDelete_Mock(t *testing.T) {
	// Mock `CreadDelete`: returns success state
	mockCredDelete := new(mockProc)
	mockCredDelete.On("Call", mock.AnythingOfType("[]uintptr")).Return(1, 0, nil)
	mockCredDelete.Setup(&procCredDelete)
	defer mockCredDelete.TearDown()

	// Test it:
	var err error
	assert.NotPanics(t, func() { err = sysCredDelete(new(Credential), sysCRED_TYPE_GENERIC) })
	assert.Nil(t, err)
	mockCredDelete.AssertNumberOfCalls(t, "Call", 1)
}

func TestSysCredEnumerate_MockFailure(t *testing.T) {
	// The test error
	testError := errors.New("test error")
	// Mock `CreadEnumerate`: returns failure state and the error
	mockCredEnumerate := new(mockProc)
	mockCredEnumerate.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, testError)
	mockCredEnumerate.Setup(&procCredEnumerate)
	defer mockCredEnumerate.TearDown()
	// Mock `CredFree`: Must not be called
	mockCredFree := new(mockProc)
	mockCredFree.On("Call", mock.AnythingOfType("[]uintptr")).Return(0, 0, nil)
	mockCredFree.Setup(&procCredFree)
	defer mockCredFree.TearDown()

	// Test it:
	var res []*Credential
	var err error
	assert.NotPanics(t, func() { res, err = sysCredEnumerate("", true) })
	assert.Nil(t, res)
	assert.NotNil(t, err)
	assert.Equal(t, "test error", err.Error())
	mockCredEnumerate.AssertNumberOfCalls(t, "Call", 1)
	mockCredFree.AssertNumberOfCalls(t, "Call", 0)
}

func TestSysCredEnumerate_Mock(t *testing.T) {
	// prepare some test data
	creds := []*Credential{new(Credential), new(Credential)}
	creds[0].TargetName = "Foo"
	creds[1].TargetName = "Bar"
	credsSys := [](*sysCREDENTIAL){
		sysFromCredential(creds[0]),
		sysFromCredential(creds[1]),
	}
	t.Log(credsSys[0]) // Workaround to keep the object alive
	t.Log(credsSys[1]) // Workaround to keep the object alive

	// Mock `CreadEnumerate`: returns success and sets the pointer to the prepared sysCreds array
	mockCredEnumerate := new(mockProc)
	mockCredEnumerate.
		On("Call", mock.AnythingOfType("[]uintptr")).
		Return(1, 0, nil).
		Run(func(args mock.Arguments) {
			arg := args.Get(0).([]uintptr)
			assert.Equal(t, 4, len(arg))
			*(*int)(unsafe.Pointer(arg[2])) = len(credsSys)
			*(*[]*sysCREDENTIAL)(unsafe.Pointer(arg[3])) = credsSys
		})
	mockCredEnumerate.Setup(&procCredEnumerate)
	defer mockCredEnumerate.TearDown()

	// Mock `CredFree`: Must be called as well with the correct pointer
	mockCredFree := new(mockProc)
	mockCredFree.
		On("Call", mock.AnythingOfType("[]uintptr")).
		Return(0, 0, nil).
		Run(func(args mock.Arguments) {
			arg := args.Get(0).([]uintptr)
			assert.Equal(t, 1, len(arg))
			assert.Equal(t, uintptr(unsafe.Pointer(&credsSys[0])), arg[0])
		})
	mockCredFree.Setup(&procCredFree)
	defer mockCredFree.TearDown()

	// Test it:
	var res []*Credential
	var err error
	assert.NotPanics(t, func() { res, err = sysCredEnumerate("", true) })
	mockCredEnumerate.AssertNumberOfCalls(t, "Call", 1)
	mockCredFree.AssertNumberOfCalls(t, "Call", 1)
	assert.NotNil(t, res)
	assert.Nil(t, err)
	assert.Equal(t, 2, len(res))
	assert.Equal(t, "Foo", res[0].TargetName)
	assert.Equal(t, "Bar", res[1].TargetName)
}
<|endoftext|>