<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 fs

import (
	"fmt"
	"sort"
	"sync"

	"github.com/googlecloudplatform/gcsfuse/fs/inode"
	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
)

// State required for reading from directories.
type dirHandle struct {
	/////////////////////////
	// Constant data
	/////////////////////////

	in           *inode.DirInode
	implicitDirs bool

	/////////////////////////
	// Mutable state
	/////////////////////////

	Mu syncutil.InvariantMutex

	// All entries in the directory. Populated the first time we need one.
	//
	// INVARIANT: For each i, entries[i+1].Offset == entries[i].Offset + 1
	//
	// GUARDED_BY(Mu)
	entries []fuseutil.Dirent

	// Has entries yet been populated?
	//
	// INVARIANT: If !entriesValid, then len(entries) == 0
	//
	// GUARDED_BY(Mu)
	entriesValid bool
}

// Create a directory handle that obtains listings from the supplied inode.
func newDirHandle(
	in *inode.DirInode,
	implicitDirs bool) (dh *dirHandle) {
	// Set up the basic struct.
	dh = &dirHandle{
		in:           in,
		implicitDirs: implicitDirs,
	}

	// Set up invariant checking.
	dh.Mu = syncutil.NewInvariantMutex(dh.checkInvariants)

	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Dirents, sorted by name.
type sortedDirents []fuseutil.Dirent

func (p sortedDirents) Len() int           { return len(p) }
func (p sortedDirents) Less(i, j int) bool { return p[i].Name < p[j].Name }
func (p sortedDirents) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func (dh *dirHandle) checkInvariants() {
	// INVARIANT: For each i, entries[i+1].Offset == entries[i].Offset + 1
	for i := 0; i < len(dh.entries)-1; i++ {
		if !(dh.entries[i+1].Offset == dh.entries[i].Offset+1) {
			panic(
				fmt.Sprintf(
					"Unexpected offset sequence: %v, %v",
					dh.entries[i].Offset,
					dh.entries[i+1].Offset))
		}
	}

	// INVARIANT: If !entriesValid, then len(entries) == 0
	if !dh.entriesValid && len(dh.entries) != 0 {
		panic("Unexpected non-empty entries slice")
	}
}

// Read some entries from the directory inode. Return newTok == "" (possibly
// with a non-empty list of entries) when the end of the directory has been
// hit.
//
// The contents of the entries' Offset fields are undefined. This function
// always behaves as if implicit directories are defined; see notes on
// DirInode.ReadEntries.
func readSomeEntries(
	ctx context.Context,
	in *inode.DirInode,
	tok string) (entries []fuseutil.Dirent, newTok string, err error) {
	entries, newTok, err = in.ReadEntries(ctx, tok)
	if err != nil {
		err = fmt.Errorf("ReadEntries: %v", err)
		return
	}

	// Return a bogus inode ID for each entry, but not the root inode ID.
	//
	// NOTE(jacobsa): As far as I can tell this is harmless. Minting and
	// returning a real inode ID is difficult because fuse does not count
	// readdir as an operation that increases the inode ID's lookup count and
	// we therefore don't get a forget for it later, but we would like to not
	// have to remember every inode ID that we've ever minted for readdir.
	//
	// If it turns out this is not harmless, we'll need to switch to something
	// like inode IDs based on (object name, generation) hashes. But then what
	// about the birthday problem? And more importantly, what about our
	// semantic of not minting a new inode ID when the generation changes due
	// to a local action?
	for i, _ := range entries {
		entries[i].Inode = fuseops.RootInodeID + 1
	}

	return
}

// Read all entries for the directory, making no effort to deal with
// conflicting names or the lack of implicit directories (see notes on
// DirInode.ReadEntries).
//
// Write entries to the supplied channel, without closing. Entry Offset fields
// have unspecified contents.
func readEntries(
	ctx context.Context,
	in *inode.DirInode,
	entries chan<- fuseutil.Dirent) (err error) {
	var tok string
	for {
		// Read a batch.
		var batch []fuseutil.Dirent

		batch, tok, err = readSomeEntries(ctx, in, tok)
		if err != nil {
			return
		}

		// Write each to the channel.
		for _, e := range batch {
			select {
			case <-ctx.Done():
				err = ctx.Err()
				return

			case entries <- e:
			}
		}

		// Are we done?
		if tok == "" {
			break
		}
	}

	return
}

// Filter out entries with type DT_Directory for which in.LookUpChild does not
// return a directory. This can be used to implicit directories without a
// matching backing object from the output of DirInode.ReadDir, which always
// behaves as if implicit directories are enabled.
func filterMissingDirectories(
	ctx context.Context,
	in *inode.DirInode,
	entriesIn <-chan fuseutil.Dirent,
	entriesOut chan<- fuseutil.Dirent) (err error) {
	for e := range entriesIn {
		// If this is a directory, confirm it actually exists.
		if e.Type == fuseutil.DT_Directory {
			var o *gcs.Object
			o, err = in.LookUpChild(ctx, e.Name)

			// Skip this entry if we failed to find anything.
			//
			// TODO(jacobsa): Return nil, nil from LookUpChild in this case and
			// simplify calling code.
			if err == fuse.ENOENT {
				continue
			}

			// Propagate other errors.
			if err != nil {
				err = fmt.Errorf("LookUpChild: %v", err)
				return
			}

			// Skip this entry if the result is not a directory.
			if !isDirName(o.Name) {
				continue
			}
		}

		// Pass on the entry.
		select {
		case <-ctx.Done():
			err = ctx.Err()
			return

		case entriesOut <- e:
		}
	}

	return
}

// Resolve name conflicts between file objects and directory objects (e.g. the
// objects "foo/bar" and "foo/bar/") by appending U+000A, which is illegal in
// GCS object names, to conflicting file names.
//
// Input must be sorted by name.
func fixConflictingNames(entries []fuseutil.Dirent) (err error) {
	// Sanity check.
	if !sort.IsSorted(sortedDirents(entries)) {
		err = fmt.Errorf("Expected sorted input")
		return
	}

	// Examine each adjacent pair of names.
	for i, _ := range entries {
		e := &entries[i]

		// Find the previous entry.
		if i == 0 {
			continue
		}

		prev := &entries[i-1]

		// Does the pair have matching names?
		if e.Name != prev.Name {
			continue
		}

		// Repair whichever is the file, remembering that there's no way to get any
		// other type in the mix.
		if e.Type == fuseutil.DT_File {
			e.Name += inode.ConflictingFileNameSuffix
		} else {
			if prev.Type != fuseutil.DT_File {
				panic(fmt.Sprintf("Unexpected type for entry: %v", prev))
			}
			prev.Name += inode.ConflictingFileNameSuffix
		}
	}

	return
}

func (dh *dirHandle) readAllEntries(
	ctx context.Context) (entries []fuseutil.Dirent, err error) {
	b := syncutil.NewBundle(ctx)

	// Read entries into a channel.
	unfiltered := make(chan fuseutil.Dirent, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(unfiltered)
		err = readEntries(ctx, dh.in, unfiltered)
		return
	})

	// If implicit directories are disabled, filter out entries for
	// sub-directories without a matching backing object.
	var filtered chan fuseutil.Dirent
	if dh.implicitDirs {
		filtered = unfiltered
	} else {
		filtered = make(chan fuseutil.Dirent, 100)

		var wg sync.WaitGroup
		f := func(ctx context.Context) error {
			defer wg.Done()
			return filterMissingDirectories(ctx, dh.in, unfiltered, filtered)
		}

		// Bound the parallelism with which we call the bucket.
		const filterWorkers = 32
		for i := 0; i < filterWorkers; i++ {
			wg.Add(1)
			b.Add(f)
		}

		go func() {
			wg.Wait()
			close(filtered)
		}()
	}

	// Accumulate entries into the slice.
	b.Add(func(ctx context.Context) (err error) {
		for e := range filtered {
			entries = append(entries, e)
		}

		return
	})

	// Wait.
	err = b.Join()
	if err != nil {
		return
	}

	// Ensure that the entries are sorted, for use in fixConflictingNames
	// below.
	sort.Sort(sortedDirents(entries))

	// Fix name conflicts.
	err = fixConflictingNames(entries)
	if err != nil {
		err = fmt.Errorf("fixConflictingNames: %v", err)
		return
	}

	// Fix up offset fields.
	for i := 0; i < len(entries); i++ {
		entries[i].Offset = fuseops.DirOffset(i) + 1
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

// Handle a request to read from the directory, without responding.
//
// Special case: we assume that a zero offset indicates that rewinddir has been
// called (since fuse gives us no way to intercept and know for sure), and
// start the listing process over again.
//
// LOCKS_REQUIRED(dh.Mu)
func (dh *dirHandle) ReadDir(
	op *fuseops.ReadDirOp) (err error) {
	// If the request is for offset zero, we assume that either this is the first
	// call or rewinddir has been called. Reset state.
	if op.Offset == 0 {
		dh.entries = nil
		dh.entriesValid = false
	}

	// Do we need to read entries from GCS?
	if !dh.entriesValid {
		// Read entries.
		var entries []fuseutil.Dirent
		entries, err = dh.readAllEntries(op.Context())
		if err != nil {
			err = fmt.Errorf("readAllEntries: %v", err)
			return
		}

		// Update state.
		dh.entries = entries
		dh.entriesValid = true
	}

	// Is the offset past the end of what we have buffered? If so, this must be
	// an invalid seekdir according to posix.
	index := int(op.Offset)
	if index > len(dh.entries) {
		err = fuse.EINVAL
		return
	}

	// We copy out entries until we run out of entries or space.
	for i := index; i < len(dh.entries); i++ {
		op.Data = fuseutil.AppendDirent(op.Data, dh.entries[i])
		if len(op.Data) > op.Size {
			op.Data = op.Data[:op.Size]
			break
		}
	}

	return
}
<commit_msg>Fixed a bug in filterMissingDirectories.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 fs

import (
	"fmt"
	"sort"
	"sync"

	"github.com/googlecloudplatform/gcsfuse/fs/inode"
	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
)

// State required for reading from directories.
type dirHandle struct {
	/////////////////////////
	// Constant data
	/////////////////////////

	in           *inode.DirInode
	implicitDirs bool

	/////////////////////////
	// Mutable state
	/////////////////////////

	Mu syncutil.InvariantMutex

	// All entries in the directory. Populated the first time we need one.
	//
	// INVARIANT: For each i, entries[i+1].Offset == entries[i].Offset + 1
	//
	// GUARDED_BY(Mu)
	entries []fuseutil.Dirent

	// Has entries yet been populated?
	//
	// INVARIANT: If !entriesValid, then len(entries) == 0
	//
	// GUARDED_BY(Mu)
	entriesValid bool
}

// Create a directory handle that obtains listings from the supplied inode.
func newDirHandle(
	in *inode.DirInode,
	implicitDirs bool) (dh *dirHandle) {
	// Set up the basic struct.
	dh = &dirHandle{
		in:           in,
		implicitDirs: implicitDirs,
	}

	// Set up invariant checking.
	dh.Mu = syncutil.NewInvariantMutex(dh.checkInvariants)

	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Dirents, sorted by name.
type sortedDirents []fuseutil.Dirent

func (p sortedDirents) Len() int           { return len(p) }
func (p sortedDirents) Less(i, j int) bool { return p[i].Name < p[j].Name }
func (p sortedDirents) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

func (dh *dirHandle) checkInvariants() {
	// INVARIANT: For each i, entries[i+1].Offset == entries[i].Offset + 1
	for i := 0; i < len(dh.entries)-1; i++ {
		if !(dh.entries[i+1].Offset == dh.entries[i].Offset+1) {
			panic(
				fmt.Sprintf(
					"Unexpected offset sequence: %v, %v",
					dh.entries[i].Offset,
					dh.entries[i+1].Offset))
		}
	}

	// INVARIANT: If !entriesValid, then len(entries) == 0
	if !dh.entriesValid && len(dh.entries) != 0 {
		panic("Unexpected non-empty entries slice")
	}
}

// Read some entries from the directory inode. Return newTok == "" (possibly
// with a non-empty list of entries) when the end of the directory has been
// hit.
//
// The contents of the entries' Offset fields are undefined. This function
// always behaves as if implicit directories are defined; see notes on
// DirInode.ReadEntries.
func readSomeEntries(
	ctx context.Context,
	in *inode.DirInode,
	tok string) (entries []fuseutil.Dirent, newTok string, err error) {
	entries, newTok, err = in.ReadEntries(ctx, tok)
	if err != nil {
		err = fmt.Errorf("ReadEntries: %v", err)
		return
	}

	// Return a bogus inode ID for each entry, but not the root inode ID.
	//
	// NOTE(jacobsa): As far as I can tell this is harmless. Minting and
	// returning a real inode ID is difficult because fuse does not count
	// readdir as an operation that increases the inode ID's lookup count and
	// we therefore don't get a forget for it later, but we would like to not
	// have to remember every inode ID that we've ever minted for readdir.
	//
	// If it turns out this is not harmless, we'll need to switch to something
	// like inode IDs based on (object name, generation) hashes. But then what
	// about the birthday problem? And more importantly, what about our
	// semantic of not minting a new inode ID when the generation changes due
	// to a local action?
	for i, _ := range entries {
		entries[i].Inode = fuseops.RootInodeID + 1
	}

	return
}

// Read all entries for the directory, making no effort to deal with
// conflicting names or the lack of implicit directories (see notes on
// DirInode.ReadEntries).
//
// Write entries to the supplied channel, without closing. Entry Offset fields
// have unspecified contents.
func readEntries(
	ctx context.Context,
	in *inode.DirInode,
	entries chan<- fuseutil.Dirent) (err error) {
	var tok string
	for {
		// Read a batch.
		var batch []fuseutil.Dirent

		batch, tok, err = readSomeEntries(ctx, in, tok)
		if err != nil {
			return
		}

		// Write each to the channel.
		for _, e := range batch {
			select {
			case <-ctx.Done():
				err = ctx.Err()
				return

			case entries <- e:
			}
		}

		// Are we done?
		if tok == "" {
			break
		}
	}

	return
}

// Filter out entries with type DT_Directory for which in.LookUpChild does not
// return a directory. This can be used to implicit directories without a
// matching backing object from the output of DirInode.ReadDir, which always
// behaves as if implicit directories are enabled.
func filterMissingDirectories(
	ctx context.Context,
	in *inode.DirInode,
	entriesIn <-chan fuseutil.Dirent,
	entriesOut chan<- fuseutil.Dirent) (err error) {
	for e := range entriesIn {
		// If this is a directory, confirm it actually exists.
		if e.Type == fuseutil.DT_Directory {
			var o *gcs.Object
			o, err = in.LookUpChild(ctx, e.Name)

			// Skip this entry if we failed to find anything.
			//
			// TODO(jacobsa): Return nil, nil from LookUpChild in this case and
			// simplify calling code.
			if err == fuse.ENOENT {
				err = nil
				continue
			}

			// Propagate other errors.
			if err != nil {
				err = fmt.Errorf("LookUpChild: %v", err)
				return
			}

			// Skip this entry if the result is not a directory.
			if !isDirName(o.Name) {
				continue
			}
		}

		// Pass on the entry.
		select {
		case <-ctx.Done():
			err = ctx.Err()
			return

		case entriesOut <- e:
		}
	}

	return
}

// Resolve name conflicts between file objects and directory objects (e.g. the
// objects "foo/bar" and "foo/bar/") by appending U+000A, which is illegal in
// GCS object names, to conflicting file names.
//
// Input must be sorted by name.
func fixConflictingNames(entries []fuseutil.Dirent) (err error) {
	// Sanity check.
	if !sort.IsSorted(sortedDirents(entries)) {
		err = fmt.Errorf("Expected sorted input")
		return
	}

	// Examine each adjacent pair of names.
	for i, _ := range entries {
		e := &entries[i]

		// Find the previous entry.
		if i == 0 {
			continue
		}

		prev := &entries[i-1]

		// Does the pair have matching names?
		if e.Name != prev.Name {
			continue
		}

		// Repair whichever is the file, remembering that there's no way to get any
		// other type in the mix.
		if e.Type == fuseutil.DT_File {
			e.Name += inode.ConflictingFileNameSuffix
		} else {
			if prev.Type != fuseutil.DT_File {
				panic(fmt.Sprintf("Unexpected type for entry: %v", prev))
			}
			prev.Name += inode.ConflictingFileNameSuffix
		}
	}

	return
}

func (dh *dirHandle) readAllEntries(
	ctx context.Context) (entries []fuseutil.Dirent, err error) {
	b := syncutil.NewBundle(ctx)

	// Read entries into a channel.
	unfiltered := make(chan fuseutil.Dirent, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(unfiltered)
		err = readEntries(ctx, dh.in, unfiltered)
		return
	})

	// If implicit directories are disabled, filter out entries for
	// sub-directories without a matching backing object.
	var filtered chan fuseutil.Dirent
	if dh.implicitDirs {
		filtered = unfiltered
	} else {
		filtered = make(chan fuseutil.Dirent, 100)

		var wg sync.WaitGroup
		f := func(ctx context.Context) error {
			defer wg.Done()
			return filterMissingDirectories(ctx, dh.in, unfiltered, filtered)
		}

		// Bound the parallelism with which we call the bucket.
		const filterWorkers = 32
		for i := 0; i < filterWorkers; i++ {
			wg.Add(1)
			b.Add(f)
		}

		go func() {
			wg.Wait()
			close(filtered)
		}()
	}

	// Accumulate entries into the slice.
	b.Add(func(ctx context.Context) (err error) {
		for e := range filtered {
			entries = append(entries, e)
		}

		return
	})

	// Wait.
	err = b.Join()
	if err != nil {
		return
	}

	// Ensure that the entries are sorted, for use in fixConflictingNames
	// below.
	sort.Sort(sortedDirents(entries))

	// Fix name conflicts.
	err = fixConflictingNames(entries)
	if err != nil {
		err = fmt.Errorf("fixConflictingNames: %v", err)
		return
	}

	// Fix up offset fields.
	for i := 0; i < len(entries); i++ {
		entries[i].Offset = fuseops.DirOffset(i) + 1
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

// Handle a request to read from the directory, without responding.
//
// Special case: we assume that a zero offset indicates that rewinddir has been
// called (since fuse gives us no way to intercept and know for sure), and
// start the listing process over again.
//
// LOCKS_REQUIRED(dh.Mu)
func (dh *dirHandle) ReadDir(
	op *fuseops.ReadDirOp) (err error) {
	// If the request is for offset zero, we assume that either this is the first
	// call or rewinddir has been called. Reset state.
	if op.Offset == 0 {
		dh.entries = nil
		dh.entriesValid = false
	}

	// Do we need to read entries from GCS?
	if !dh.entriesValid {
		// Read entries.
		var entries []fuseutil.Dirent
		entries, err = dh.readAllEntries(op.Context())
		if err != nil {
			err = fmt.Errorf("readAllEntries: %v", err)
			return
		}

		// Update state.
		dh.entries = entries
		dh.entriesValid = true
	}

	// Is the offset past the end of what we have buffered? If so, this must be
	// an invalid seekdir according to posix.
	index := int(op.Offset)
	if index > len(dh.entries) {
		err = fuse.EINVAL
		return
	}

	// We copy out entries until we run out of entries or space.
	for i := index; i < len(dh.entries); i++ {
		op.Data = fuseutil.AppendDirent(op.Data, dh.entries[i])
		if len(op.Data) > op.Size {
			op.Data = op.Data[:op.Size]
			break
		}
	}

	return
}
<|endoftext|>


<commit_before>//go:build linux || freebsd
// +build linux freebsd

package buildah

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/containers/common/libnetwork/etchosts"
	"github.com/containers/common/libnetwork/resolvconf"
	"github.com/containers/common/pkg/config"
	"github.com/containers/storage/pkg/idtools"
	"github.com/containers/storage/pkg/ioutils"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/opencontainers/runtime-tools/generate"
	"github.com/opencontainers/selinux/go-selinux/label"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
	"golang.org/x/term"
)

// addResolvConf copies files from host and sets them up to bind mount into container
func (b *Builder) addResolvConf(rdir string, chownOpts *idtools.IDPair, dnsServers, dnsSearch, dnsOptions []string, namespaces []specs.LinuxNamespace) (string, error) {
	defaultConfig, err := config.Default()
	if err != nil {
		return "", fmt.Errorf("failed to get config: %w", err)
	}

	nameservers := make([]string, 0, len(defaultConfig.Containers.DNSServers)+len(dnsServers))
	nameservers = append(nameservers, defaultConfig.Containers.DNSServers...)
	nameservers = append(nameservers, dnsServers...)

	keepHostServers := false
	// special check for slirp ip
	if len(nameservers) == 0 && b.Isolation == IsolationOCIRootless {
		for _, ns := range namespaces {
			if ns.Type == specs.NetworkNamespace && ns.Path == "" {
				keepHostServers = true
				// if we are using slirp4netns, also add the built-in DNS server.
				logrus.Debugf("adding slirp4netns 10.0.2.3 built-in DNS server")
				nameservers = append([]string{"10.0.2.3"}, nameservers...)
			}
		}
	}

	searches := make([]string, 0, len(defaultConfig.Containers.DNSSearches)+len(dnsSearch))
	searches = append(searches, defaultConfig.Containers.DNSSearches...)
	searches = append(searches, dnsSearch...)

	options := make([]string, 0, len(defaultConfig.Containers.DNSOptions)+len(dnsOptions))
	options = append(options, defaultConfig.Containers.DNSOptions...)
	options = append(options, dnsOptions...)

	cfile := filepath.Join(rdir, "resolv.conf")
	if err := resolvconf.New(&resolvconf.Params{
		Path:            cfile,
		Namespaces:      namespaces,
		IPv6Enabled:     true, // TODO we should check if we have ipv6
		KeepHostServers: keepHostServers,
		Nameservers:     nameservers,
		Searches:        searches,
		Options:         options,
	}); err != nil {
		return "", fmt.Errorf("error building resolv.conf for container %s: %w", b.ContainerID, err)
	}

	uid := 0
	gid := 0
	if chownOpts != nil {
		uid = chownOpts.UID
		gid = chownOpts.GID
	}
	if err = os.Chown(cfile, uid, gid); err != nil {
		return "", err
	}

	if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
		return "", err
	}
	return cfile, nil
}

// generateHosts creates a containers hosts file
func (b *Builder) generateHosts(rdir string, chownOpts *idtools.IDPair, imageRoot string) (string, error) {
	conf, err := config.Default()
	if err != nil {
		return "", err
	}

	path, err := etchosts.GetBaseHostFile(conf.Containers.BaseHostsFile, imageRoot)
	if err != nil {
		return "", err
	}

	targetfile := filepath.Join(rdir, "hosts")
	if err := etchosts.New(&etchosts.Params{
		BaseFile:                 path,
		ExtraHosts:               b.CommonBuildOpts.AddHost,
		HostContainersInternalIP: etchosts.GetHostContainersInternalIP(conf, nil, nil),
		TargetFile:               targetfile,
	}); err != nil {
		return "", err
	}

	uid := 0
	gid := 0
	if chownOpts != nil {
		uid = chownOpts.UID
		gid = chownOpts.GID
	}
	if err = os.Chown(targetfile, uid, gid); err != nil {
		return "", err
	}
	if err := label.Relabel(targetfile, b.MountLabel, false); err != nil {
		return "", err
	}

	return targetfile, nil
}

// generateHostname creates a containers /etc/hostname file
func (b *Builder) generateHostname(rdir, hostname string, chownOpts *idtools.IDPair) (string, error) {
	var err error
	hostnamePath := "/etc/hostname"

	var hostnameBuffer bytes.Buffer
	hostnameBuffer.Write([]byte(fmt.Sprintf("%s\n", hostname)))

	cfile := filepath.Join(rdir, filepath.Base(hostnamePath))
	if err = ioutils.AtomicWriteFile(cfile, hostnameBuffer.Bytes(), 0644); err != nil {
		return "", fmt.Errorf("error writing /etc/hostname into the container: %w", err)
	}

	uid := 0
	gid := 0
	if chownOpts != nil {
		uid = chownOpts.UID
		gid = chownOpts.GID
	}
	if err = os.Chown(cfile, uid, gid); err != nil {
		return "", err
	}
	if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
		return "", err
	}

	return cfile, nil
}

func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy, terminalSize *specs.Box) {
	switch terminalPolicy {
	case DefaultTerminal:
		onTerminal := term.IsTerminal(unix.Stdin) && term.IsTerminal(unix.Stdout) && term.IsTerminal(unix.Stderr)
		if onTerminal {
			logrus.Debugf("stdio is a terminal, defaulting to using a terminal")
		} else {
			logrus.Debugf("stdio is not a terminal, defaulting to not using a terminal")
		}
		g.SetProcessTerminal(onTerminal)
	case WithTerminal:
		g.SetProcessTerminal(true)
	case WithoutTerminal:
		g.SetProcessTerminal(false)
	}
	if terminalSize != nil {
		g.SetProcessConsoleSize(terminalSize.Width, terminalSize.Height)
	}
}

// Search for a command that isn't given as an absolute path using the $PATH
// under the rootfs.  We can't resolve absolute symbolic links without
// chroot()ing, which we may not be able to do, so just accept a link as a
// valid resolution.
func runLookupPath(g *generate.Generator, command []string) []string {
	// Look for the configured $PATH.
	spec := g.Config
	envPath := ""
	for i := range spec.Process.Env {
		if strings.HasPrefix(spec.Process.Env[i], "PATH=") {
			envPath = spec.Process.Env[i]
		}
	}
	// If there is no configured $PATH, supply one.
	if envPath == "" {
		defaultPath := "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
		envPath = "PATH=" + defaultPath
		g.AddProcessEnv("PATH", defaultPath)
	}
	// No command, nothing to do.
	if len(command) == 0 {
		return command
	}
	// Command is already an absolute path, use it as-is.
	if filepath.IsAbs(command[0]) {
		return command
	}
	// For each element in the PATH,
	for _, pathEntry := range filepath.SplitList(envPath[5:]) {
		// if it's the empty string, it's ".", which is the Cwd,
		if pathEntry == "" {
			pathEntry = spec.Process.Cwd
		}
		// build the absolute path which it might be,
		candidate := filepath.Join(pathEntry, command[0])
		// check if it's there,
		if fi, err := os.Lstat(filepath.Join(spec.Root.Path, candidate)); fi != nil && err == nil {
			// and if it's not a directory, and either a symlink or executable,
			if !fi.IsDir() && ((fi.Mode()&os.ModeSymlink != 0) || (fi.Mode()&0111 != 0)) {
				// use that.
				return append([]string{candidate}, command[1:]...)
			}
		}
	}
	return command
}

func (b *Builder) configureUIDGID(g *generate.Generator, mountPoint string, options RunOptions) (string, error) {
	// Set the user UID/GID/supplemental group list/capabilities lists.
	user, homeDir, err := b.userForRun(mountPoint, options.User)
	if err != nil {
		return "", err
	}
	if err := setupCapabilities(g, b.Capabilities, options.AddCapabilities, options.DropCapabilities); err != nil {
		return "", err
	}
	g.SetProcessUID(user.UID)
	g.SetProcessGID(user.GID)
	for _, gid := range user.AdditionalGids {
		g.AddProcessAdditionalGid(gid)
	}

	// Remove capabilities if not running as root except Bounding set
	if user.UID != 0 {
		bounding := g.Config.Process.Capabilities.Bounding
		g.ClearProcessCapabilities()
		g.Config.Process.Capabilities.Bounding = bounding
	}

	return homeDir, nil
}
<commit_msg>Don't crash in configureUIDGID if Process.Capabilities is nil<commit_after>//go:build linux || freebsd
// +build linux freebsd

package buildah

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/containers/common/libnetwork/etchosts"
	"github.com/containers/common/libnetwork/resolvconf"
	"github.com/containers/common/pkg/config"
	"github.com/containers/storage/pkg/idtools"
	"github.com/containers/storage/pkg/ioutils"
	"github.com/opencontainers/runtime-spec/specs-go"
	"github.com/opencontainers/runtime-tools/generate"
	"github.com/opencontainers/selinux/go-selinux/label"
	"github.com/sirupsen/logrus"
	"golang.org/x/sys/unix"
	"golang.org/x/term"
)

// addResolvConf copies files from host and sets them up to bind mount into container
func (b *Builder) addResolvConf(rdir string, chownOpts *idtools.IDPair, dnsServers, dnsSearch, dnsOptions []string, namespaces []specs.LinuxNamespace) (string, error) {
	defaultConfig, err := config.Default()
	if err != nil {
		return "", fmt.Errorf("failed to get config: %w", err)
	}

	nameservers := make([]string, 0, len(defaultConfig.Containers.DNSServers)+len(dnsServers))
	nameservers = append(nameservers, defaultConfig.Containers.DNSServers...)
	nameservers = append(nameservers, dnsServers...)

	keepHostServers := false
	// special check for slirp ip
	if len(nameservers) == 0 && b.Isolation == IsolationOCIRootless {
		for _, ns := range namespaces {
			if ns.Type == specs.NetworkNamespace && ns.Path == "" {
				keepHostServers = true
				// if we are using slirp4netns, also add the built-in DNS server.
				logrus.Debugf("adding slirp4netns 10.0.2.3 built-in DNS server")
				nameservers = append([]string{"10.0.2.3"}, nameservers...)
			}
		}
	}

	searches := make([]string, 0, len(defaultConfig.Containers.DNSSearches)+len(dnsSearch))
	searches = append(searches, defaultConfig.Containers.DNSSearches...)
	searches = append(searches, dnsSearch...)

	options := make([]string, 0, len(defaultConfig.Containers.DNSOptions)+len(dnsOptions))
	options = append(options, defaultConfig.Containers.DNSOptions...)
	options = append(options, dnsOptions...)

	cfile := filepath.Join(rdir, "resolv.conf")
	if err := resolvconf.New(&resolvconf.Params{
		Path:            cfile,
		Namespaces:      namespaces,
		IPv6Enabled:     true, // TODO we should check if we have ipv6
		KeepHostServers: keepHostServers,
		Nameservers:     nameservers,
		Searches:        searches,
		Options:         options,
	}); err != nil {
		return "", fmt.Errorf("error building resolv.conf for container %s: %w", b.ContainerID, err)
	}

	uid := 0
	gid := 0
	if chownOpts != nil {
		uid = chownOpts.UID
		gid = chownOpts.GID
	}
	if err = os.Chown(cfile, uid, gid); err != nil {
		return "", err
	}

	if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
		return "", err
	}
	return cfile, nil
}

// generateHosts creates a containers hosts file
func (b *Builder) generateHosts(rdir string, chownOpts *idtools.IDPair, imageRoot string) (string, error) {
	conf, err := config.Default()
	if err != nil {
		return "", err
	}

	path, err := etchosts.GetBaseHostFile(conf.Containers.BaseHostsFile, imageRoot)
	if err != nil {
		return "", err
	}

	targetfile := filepath.Join(rdir, "hosts")
	if err := etchosts.New(&etchosts.Params{
		BaseFile:                 path,
		ExtraHosts:               b.CommonBuildOpts.AddHost,
		HostContainersInternalIP: etchosts.GetHostContainersInternalIP(conf, nil, nil),
		TargetFile:               targetfile,
	}); err != nil {
		return "", err
	}

	uid := 0
	gid := 0
	if chownOpts != nil {
		uid = chownOpts.UID
		gid = chownOpts.GID
	}
	if err = os.Chown(targetfile, uid, gid); err != nil {
		return "", err
	}
	if err := label.Relabel(targetfile, b.MountLabel, false); err != nil {
		return "", err
	}

	return targetfile, nil
}

// generateHostname creates a containers /etc/hostname file
func (b *Builder) generateHostname(rdir, hostname string, chownOpts *idtools.IDPair) (string, error) {
	var err error
	hostnamePath := "/etc/hostname"

	var hostnameBuffer bytes.Buffer
	hostnameBuffer.Write([]byte(fmt.Sprintf("%s\n", hostname)))

	cfile := filepath.Join(rdir, filepath.Base(hostnamePath))
	if err = ioutils.AtomicWriteFile(cfile, hostnameBuffer.Bytes(), 0644); err != nil {
		return "", fmt.Errorf("error writing /etc/hostname into the container: %w", err)
	}

	uid := 0
	gid := 0
	if chownOpts != nil {
		uid = chownOpts.UID
		gid = chownOpts.GID
	}
	if err = os.Chown(cfile, uid, gid); err != nil {
		return "", err
	}
	if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
		return "", err
	}

	return cfile, nil
}

func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy, terminalSize *specs.Box) {
	switch terminalPolicy {
	case DefaultTerminal:
		onTerminal := term.IsTerminal(unix.Stdin) && term.IsTerminal(unix.Stdout) && term.IsTerminal(unix.Stderr)
		if onTerminal {
			logrus.Debugf("stdio is a terminal, defaulting to using a terminal")
		} else {
			logrus.Debugf("stdio is not a terminal, defaulting to not using a terminal")
		}
		g.SetProcessTerminal(onTerminal)
	case WithTerminal:
		g.SetProcessTerminal(true)
	case WithoutTerminal:
		g.SetProcessTerminal(false)
	}
	if terminalSize != nil {
		g.SetProcessConsoleSize(terminalSize.Width, terminalSize.Height)
	}
}

// Search for a command that isn't given as an absolute path using the $PATH
// under the rootfs.  We can't resolve absolute symbolic links without
// chroot()ing, which we may not be able to do, so just accept a link as a
// valid resolution.
func runLookupPath(g *generate.Generator, command []string) []string {
	// Look for the configured $PATH.
	spec := g.Config
	envPath := ""
	for i := range spec.Process.Env {
		if strings.HasPrefix(spec.Process.Env[i], "PATH=") {
			envPath = spec.Process.Env[i]
		}
	}
	// If there is no configured $PATH, supply one.
	if envPath == "" {
		defaultPath := "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
		envPath = "PATH=" + defaultPath
		g.AddProcessEnv("PATH", defaultPath)
	}
	// No command, nothing to do.
	if len(command) == 0 {
		return command
	}
	// Command is already an absolute path, use it as-is.
	if filepath.IsAbs(command[0]) {
		return command
	}
	// For each element in the PATH,
	for _, pathEntry := range filepath.SplitList(envPath[5:]) {
		// if it's the empty string, it's ".", which is the Cwd,
		if pathEntry == "" {
			pathEntry = spec.Process.Cwd
		}
		// build the absolute path which it might be,
		candidate := filepath.Join(pathEntry, command[0])
		// check if it's there,
		if fi, err := os.Lstat(filepath.Join(spec.Root.Path, candidate)); fi != nil && err == nil {
			// and if it's not a directory, and either a symlink or executable,
			if !fi.IsDir() && ((fi.Mode()&os.ModeSymlink != 0) || (fi.Mode()&0111 != 0)) {
				// use that.
				return append([]string{candidate}, command[1:]...)
			}
		}
	}
	return command
}

func (b *Builder) configureUIDGID(g *generate.Generator, mountPoint string, options RunOptions) (string, error) {
	// Set the user UID/GID/supplemental group list/capabilities lists.
	user, homeDir, err := b.userForRun(mountPoint, options.User)
	if err != nil {
		return "", err
	}
	if err := setupCapabilities(g, b.Capabilities, options.AddCapabilities, options.DropCapabilities); err != nil {
		return "", err
	}
	g.SetProcessUID(user.UID)
	g.SetProcessGID(user.GID)
	for _, gid := range user.AdditionalGids {
		g.AddProcessAdditionalGid(gid)
	}

	// Remove capabilities if not running as root except Bounding set
	if user.UID != 0 && g.Config.Process.Capabilities != nil {
		bounding := g.Config.Process.Capabilities.Bounding
		g.ClearProcessCapabilities()
		g.Config.Process.Capabilities.Bounding = bounding
	}

	return homeDir, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const ucdFileName = "UnicodeData.txt"
const ucdBaseUrl = "http://www.unicode.org/Public/UCD/latest/ucd/"

func progress(done *bool) {
	for !*done {
		fmt.Print(".")
		time.Sleep(200 * time.Millisecond)
	}
	fmt.Println()
}

func getUcdFile(fileName string) {
	url := ucdBaseUrl + ucdFileName
	fmt.Printf("%s not found\nretrieving from %s\n", ucdFileName, url)
	response, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	file, err := os.Create(fileName)
	if err != nil {
		panic(err)
	}
	_, err = io.Copy(file, response.Body)
	if err != nil {
		panic(err)
	}
	file.Close()
}

func loadIndex(fileName string) (map[string][]rune, map[rune]string) {
	if _, err := os.Stat(fileName); os.IsNotExist(err) {
		done := false
		go progress(&done)
		getUcdFile(fileName)
		done = true
	}
	content, err := ioutil.ReadFile(fileName)
	if err != nil {
		panic(err)
	}

	lines := strings.Split(string(content), "\n")

	index := make(map[string][]rune)
	names := make(map[rune]string)

	for _, line := range lines {
		var uchar rune
		fields := strings.Split(line, ";")
		if len(fields) >= 2 {
			code64, _ := strconv.ParseInt(fields[0], 16, 0)
			uchar = rune(code64)
			names[uchar] = fields[1]
			for _, word := range strings.Split(fields[1], " ") {
				var entries []rune
				if len(index[word]) < 1 {
					entries = make([]rune, 0)
				} else {
					entries = index[word]
				}
				index[word] = append(entries, uchar)
			}
		}

	}
	return index, names
}

func main() {
	dir, _ := os.Getwd()
	path := path.Join(dir, ucdFileName)
	index, names := loadIndex(path)
	if len(os.Args) != 2 {
		os.Exit(1)
	}

	word := strings.ToUpper(os.Args[1])
	for _, uchar := range index[word] {
		fmt.Printf("U+%-5X %c\t%s\n", uchar, uchar, names[uchar])
	}

}
<commit_msg>usage instructions<commit_after>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const ucdFileName = "UnicodeData.txt"
const ucdBaseUrl = "http://www.unicode.org/Public/UCD/latest/ucd/"

func progress(done *bool) {
	for !*done {
		fmt.Print(".")
		time.Sleep(200 * time.Millisecond)
	}
	fmt.Println()
}

func getUcdFile(fileName string) {
	url := ucdBaseUrl + ucdFileName
	fmt.Printf("%s not found\nretrieving from %s\n", ucdFileName, url)
	response, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	file, err := os.Create(fileName)
	if err != nil {
		panic(err)
	}
	_, err = io.Copy(file, response.Body)
	if err != nil {
		panic(err)
	}
	file.Close()
}

func loadIndex(fileName string) (map[string][]rune, map[rune]string) {
	if _, err := os.Stat(fileName); os.IsNotExist(err) {
		done := false
		go progress(&done)
		getUcdFile(fileName)
		done = true
	}
	content, err := ioutil.ReadFile(fileName)
	if err != nil {
		panic(err)
	}

	lines := strings.Split(string(content), "\n")

	index := make(map[string][]rune)
	names := make(map[rune]string)

	for _, line := range lines {
		var uchar rune
		fields := strings.Split(line, ";")
		if len(fields) >= 2 {
			code64, _ := strconv.ParseInt(fields[0], 16, 0)
			uchar = rune(code64)
			names[uchar] = fields[1]
			for _, word := range strings.Split(fields[1], " ") {
				var entries []rune
				if len(index[word]) < 1 {
					entries = make([]rune, 0)
				} else {
					entries = index[word]
				}
				index[word] = append(entries, uchar)
			}
		}

	}
	return index, names
}

func main() {
	dir, _ := os.Getwd()
	path := path.Join(dir, ucdFileName)
	index, names := loadIndex(path)
	if len(os.Args) != 2 {
		fmt.Println("Usage:  runefinder <word>\texample: runefinder cat")
		os.Exit(1)
	}

	word := strings.ToUpper(os.Args[1])
	for _, uchar := range index[word] {
		fmt.Printf("U+%-5X %c\t%s\n", uchar, uchar, names[uchar])
	}

}
<|endoftext|>


<commit_before>package rtda

import (
	. "github.com/zxh0/jvm.go/jvmgo/any"
	rtc "github.com/zxh0/jvm.go/jvmgo/jvm/rtda/class"
)

type OperandStack struct {
	size  uint
	slots []Any
}

func newOperandStack(size uint) *OperandStack {
	if size > 0 {
		slots := make([]Any, size)
		return &OperandStack{0, slots}
	} else {
		return nil
	}
}

func (self *OperandStack) IsEmpty() bool {
	return self.size == 0
}

func (self *OperandStack) PushNull() {
	self.Push(nil)
}

func (self *OperandStack) PushRef(ref *rtc.Obj) {
	self.slots[self.size] = ref
	self.size++
}
func (self *OperandStack) PopRef() *rtc.Obj {
	ref := self.Pop()
	if ref == nil {
		return nil
	} else {
		return ref.(*rtc.Obj)
	}
}

func (self *OperandStack) PushBoolean(val bool) {
	if val {
		self.PushInt(1)
	} else {
		self.PushInt(0)
	}
}
func (self *OperandStack) PopBoolean() bool {
	return self.PopInt() == 1
}

func (self *OperandStack) PushInt(val int32) {
	self.slots[self.size] = val
	self.size++
}
func (self *OperandStack) PopInt() int32 {
	return self.Pop().(int32)
}

func (self *OperandStack) PushLong(val int64) {
	self.slots[self.size] = val
	self.size++
}
func (self *OperandStack) PopLong() int64 {
	return self.Pop().(int64)
}

func (self *OperandStack) PushFloat(val float32) {
	self.slots[self.size] = val
	self.size++
}
func (self *OperandStack) PopFloat() float32 {
	return self.Pop().(float32)
}

func (self *OperandStack) PushDouble(val float64) {
	self.slots[self.size] = val
	self.size++
}
func (self *OperandStack) PopDouble() float64 {
	return self.Pop().(float64)
}

func (self *OperandStack) Push(any Any) {
	self.slots[self.size] = any
	self.size++
}
func (self *OperandStack) Pop() Any {
	self.size--
	top := self.slots[self.size]
	self.slots[self.size] = nil
	return top
}

func (self *OperandStack) PopTops(n uint) []Any {
	start := self.size - n
	end := self.size
	top := self.slots[start:end]
	self.size -= n
	return top
}

func (self *OperandStack) TopRef(n uint) *rtc.Obj {
	ref := self.slots[self.size-1-n]
	if ref == nil {
		return nil
	} else {
		return ref.(*rtc.Obj)
	}
}

func (self *OperandStack) Clear() {
	self.size = 0
	for i, _ := range self.slots {
		self.slots[i] = nil
	}
}

// only used by native methods
func (self *OperandStack) HackSetSlots(slots []Any) {
	self.slots = slots
	self.size = uint(len(slots))
}
<commit_msg>code optimization<commit_after>package rtda

import (
	. "github.com/zxh0/jvm.go/jvmgo/any"
	rtc "github.com/zxh0/jvm.go/jvmgo/jvm/rtda/class"
)

type OperandStack struct {
	size  uint
	slots []Any
}

func newOperandStack(size uint) *OperandStack {
	if size > 0 {
		slots := make([]Any, size)
		return &OperandStack{0, slots}
	} else {
		return nil
	}
}

func (self *OperandStack) IsEmpty() bool {
	return self.size == 0
}

func (self *OperandStack) PushNull() {
	self.slots[self.size] = nil
	self.size++
}

func (self *OperandStack) PushRef(ref *rtc.Obj) {
	self.slots[self.size] = ref
	self.size++
}
func (self *OperandStack) PopRef() *rtc.Obj {
	self.size--
	top := self.slots[self.size]
	self.slots[self.size] = nil

	if top == nil {
		return nil
	} else {
		return top.(*rtc.Obj)
	}
}

func (self *OperandStack) PushBoolean(val bool) {
	if val {
		self.PushInt(1)
	} else {
		self.PushInt(0)
	}
}
func (self *OperandStack) PopBoolean() bool {
	return self.PopInt() == 1
}

func (self *OperandStack) PushInt(val int32) {
	self.slots[self.size] = val
	self.size++
}
func (self *OperandStack) PopInt() int32 {
	self.size--
	top := self.slots[self.size]
	self.slots[self.size] = nil
	return top.(int32)
}

func (self *OperandStack) PushLong(val int64) {
	self.slots[self.size] = val
	self.size++
}
func (self *OperandStack) PopLong() int64 {
	self.size--
	top := self.slots[self.size]
	self.slots[self.size] = nil
	return top.(int64)
}

func (self *OperandStack) PushFloat(val float32) {
	self.slots[self.size] = val
	self.size++
}
func (self *OperandStack) PopFloat() float32 {
	self.size--
	top := self.slots[self.size]
	self.slots[self.size] = nil
	return top.(float32)
}

func (self *OperandStack) PushDouble(val float64) {
	self.slots[self.size] = val
	self.size++
}
func (self *OperandStack) PopDouble() float64 {
	self.size--
	top := self.slots[self.size]
	self.slots[self.size] = nil
	return top.(float64)
}

func (self *OperandStack) Push(any Any) {
	self.slots[self.size] = any
	self.size++
}
func (self *OperandStack) Pop() Any {
	self.size--
	top := self.slots[self.size]
	self.slots[self.size] = nil
	return top
}

func (self *OperandStack) PopTops(n uint) []Any {
	start := self.size - n
	end := self.size
	top := self.slots[start:end]
	self.size -= n
	return top
}

func (self *OperandStack) TopRef(n uint) *rtc.Obj {
	ref := self.slots[self.size-1-n]
	if ref == nil {
		return nil
	} else {
		return ref.(*rtc.Obj)
	}
}

func (self *OperandStack) Clear() {
	self.size = 0
	for i, _ := range self.slots {
		self.slots[i] = nil
	}
}

// only used by native methods
func (self *OperandStack) HackSetSlots(slots []Any) {
	self.slots = slots
	self.size = uint(len(slots))
}
<|endoftext|>


<commit_before>package boardgame

import (
	"github.com/jkomoros/boardgame/enum"
)

//GameDelegate is the place that various parts of the game lifecycle can be
//modified to support this particular game. Typically you embed
//base.GameDelegate in your won struct, and only override methods whose
//default behavior is incorrect for your game.
type GameDelegate interface {

	//Name is a string that defines the type of game this is. The name should
	//be unique and compact, and avoid any special characters other than "-"
	//or "_", since they will sometimes be used in a URL path. Good examples
	//are "tictactoe", "blackjack". Once configured, names should never change
	//over the lifetime of the gametype, since it will be persisted in
	//storage. Subclasses should override this. This must return the package
	//name that contains the game (e.g. "github.com/jkomoros/mygame" should
	//return "mygame"), since the package name and the delegate.Name() are
	//both used at different times in the system, since one can be determined
	//statically and the other only at run-time. NewGameManager will fail if
	//that is not true.
	Name() string

	//DisplayName is a string that defines the type of game this is in a way
	//appropriate for humans. The name should be unique but human readable. It
	//is purely for human consumption, and may change over time with no
	//adverse effects. Good examples are "Tic Tac Toe", "Blackjack".
	//Subclasses should override this.
	DisplayName() string

	//Description is a string that describes the game type in a descriptive
	//sentence. A reasonable value for "tictactoe" is "A classic game where
	//players compete to get three in a row"
	Description() string

	//ConfigureMoves will be called during creation of a GameManager in
	//NewGameManager. This is the time to install moves onto the manager by
	//returning a list of moves to install. Typically you use moves.Combine
	//and friends to organize your list of moves to install. If the moves you
	//add are illegal for any reason, NewGameManager will fail with an error.
	//By the time this is called. delegate.SetManager will already have been
	//called, so you'll have access to the manager via Manager().
	ConfigureMoves() []MoveConfig

	//ConfigureAgents will be called when creating a new GameManager. Emit the
	//agents you want to install.
	ConfigureAgents() []Agent

	//ConfigureDecks will be called when the GameManager is being booted up.
	//Each entry in the return value will be configured on the ComponentChest
	//that is being created.
	ConfigureDecks() map[string]*Deck

	//ConfigureEnums is called during set up of a new GameManager. Return the
	//set of enums you want to be associated with this GameManagaer's Chest.
	ConfigureEnums() *enum.Set

	//ConfigureConstants is called during set-up of a new GameManager. Return
	//the map of constants you want to create, which will be configured onto
	//the newly created chest via AddConstant. If any of the AddConstant calls
	//errors, the GameManager will fail to be set up. Constants are primarily
	//useful in two cases: first, when you want to have access to a constant
	//value client-side, and second, when you want to be able to use a
	//constant value in a tag-based struct inflater.
	ConfigureConstants() PropertyCollection

	//GameStateConstructor and PlayerStateConstructor are called to get an
	//instantiation of the concrete game/player structs that your package
	//defines. This is used both to create the initial state, but also to
	//inflate states from the database. These methods should always return the
	//underlying same type of struct when called. This means that if different
	//players have very different roles in a game, there might be many
	//properties that are not in use for any given player. The simple
	//properties (ints, bools, strings) should all be their zero-value.
	//Importantly, all Stacks, Timers, and Enums should be non- nil, because
	//an initialized struct contains information about things like MaxSize,
	//Size, and a reference to the deck they are affiliated with. GameManger
	//will automatically create and use StructInflaters for these types of
	//objects, allowing you to use tag-based configuration to automatically
	//inflate these properties. See the documentation for StructInflater for
	//more. Since these two methods are always required and always specific to
	//each game type, defaultGameDelegate does not implement them, as an extra
	//reminder that you must implement them yourself.
	GameStateConstructor() ConfigurableSubState
	//PlayerStateConstructor is similar to GameStateConstructor, but
	//playerIndex is the value that this PlayerState must return when its
	//PlayerIndex() is called.
	PlayerStateConstructor(player PlayerIndex) ConfigurablePlayerState

	//DynamicComponentValuesConstructor returns an empty
	//DynamicComponentValues for the given deck. If nil is returned, then the
	//components in that deck don't have any dynamic component state. This
	//method must always return the same underlying type of struct for the
	//same deck. Like GameStateConstructor and PlayerStateConstructor, the
	//engine will automatically create StructInflaters for these objects,
	//allowing you to use tag-based inflation of properties. See StructInflate
	//for more. If the returned object also implements the ComponentValues
	//interface, then SetContainingComponent will be called on the
	//DynamicComponent whenever one is created, with a reference back to the
	//component it's associated with.
	DynamicComponentValuesConstructor(deck *Deck) ConfigurableSubState

	//DistributeComponentToStarterStack is called during set up to establish
	//the Deck/Stack invariant that every component in the chest is placed in
	//precisely one Stack. Game will call this on each component in the Chest
	//in order. This is where the logic goes to make sure each Component goes
	//into its correct starter stack. You must return a non-nil Stack for each
	//call, after which the given Component will be inserted into
	//NextSlotIndex of that stack. If that is not the ordering you desire, you
	//can fix it up in FinishSetUp by using SwapComponents. If any errors are
	//returned, any nil Stacks are returned, or any returned stacks don't have
	//space for another component, NewGame will fail and return an error.
	//State and Component are only provided for reference; do not modify them.
	DistributeComponentToStarterStack(state ImmutableState, c Component) (ImmutableStack, error)

	//BeginSetup is a chance to modify the initial state object *before* the
	//components are distributed to it. It is also where the variant
	//configuration for your gametype will be passed (it will already have
	//been checked for legality and had all configure defaults set), although
	//you can also retrieve that at any time via game.Variant(). This is a
	//good place to configure state that will be necessary for you to make the
	//right decisions in DistributeComponentToStarterStack, or to transcribe
	//config information you were passed into properties on your gameState as
	//appropriate. If error is non-nil, Game setup will be aborted, with the
	//reasoning including the error message provided.
	BeginSetUp(state State, variant Variant) error

	//FinishSetUp is called during NewGame, *after* components have been
	//distributed to their StarterStack. This is the last chance to modify the
	//state before the game's initial state is considered final. For example,
	//if you have a card game this is where you'd make sure the starter draw
	//stacks are shuffled. If your game has multiple rounds, or if you don't
	//want the game to start with it already set-up (e.g. you want to show
	//animations of starter cards being dealt) then it's probably best to do
	//most of the logic in a SetUp phase. See the README for more. If error is
	//non-nil, Game setup will be aborted, with the reasoning including the
	//error message provided.
	FinishSetUp(state State) error

	//CheckGameFinished should return true if the game is finished, and who
	//the winners are. Called after every move is applied.
	CheckGameFinished(state ImmutableState) (finished bool, winners []PlayerIndex)

	//ProposeFixUpMove is called after a move has been applied. It may return
	//a FixUp move, which will be applied before any other moves are applied.
	//If it returns nil, we may take the next move off of the queue. FixUp
	//moves are useful for things like shuffling a discard deck back into a
	//draw deck, or other moves that are necessary to get the GameState back
	//into reasonable shape.
	ProposeFixUpMove(state ImmutableState) Move

	//DefaultNumPlayers returns the number of users that this game defaults to.
	//For example, for tictactoe, it will be 2. If 0 is provided to
	//manager.NewGame(), we wil use this value instead.
	DefaultNumPlayers() int

	//Min/MaxNumPlayers should return the min and max number of players,
	//respectively. The engine doesn't use this directly, instead looking at
	//LegalNumPlayers. Typically your LegalNumPlayers will check the given
	//number of players is between these two extremes.
	MinNumPlayers() int
	MaxNumPlayers() int

	//LegalNumPlayers will be consulted when a new game is created. It should
	//return true if the given number of players is legal, and false
	//otherwise. If this returns false, the NewGame will fail with an error.
	//Game creation will automatically reject a numPlayers that does not
	//result in at least one player existing. Generally this is simply
	//checking to make sure the number of players is between Min and Max
	//(inclusive), although some games could only allow, for example, even
	//numbers of players.
	LegalNumPlayers(numPlayers int) bool

	//Variants returns a VariantConfig, which describes the different
	//categories of configuration values and the legal values they may take
	//on. Ultimately your LegalVariant is the final arbiter of which variants
	//are legal; this method is mainly used so that user interfaces know which
	//variants to show to the user. In general you shouldn't call this, but
	//instead call gameManager.Variants() which will ensure your VariantConfig
	//is initalized and memoize the return result.
	Variants() VariantConfig

	//CurrentPlayerIndex returns the index of the "current" player--a notion
	//that is game specific (and sometimes inapplicable). If CurrentPlayer
	//doesn't make sense (perhaps the game never has a notion of current
	//player, or the type of round that we're in has no current player), this
	//should return ObserverPlayerIndex. The result of this method is used to
	//power state.CurrentPlayer.
	CurrentPlayerIndex(state ImmutableState) PlayerIndex

	//CurrentPhase returns the phase that the game state is currently in.
	//Phase is a formalized convention used in moves.Default to make it easier
	//to write fix-up moves that only apply in certain phases, like SetUp. The
	//return result is primarily used in moves.Default to check whether it is
	//one of the phases in a give Move's LegalPhases. See moves.Default for
	//more information.
	CurrentPhase(state ImmutableState) int

	//PhaseEnum returns the enum for game phases (the return values of
	//CurrentPhase are expected to be valid enums within that enum). If this
	//returns a non-nil enums.TreeEnum, then the state will not be able to be
	//saved if CurrentPhase() returns a value that is not a leaf-node.
	PhaseEnum() enum.Enum

	//SanitizationPolicy is consulted when sanitizing states. It is called for
	//each prop in the state, including the set of groups that this player is
	//a mamber of. In practice the default behavior of defaultGameDelegate,
	//which uses struct tags to figure out the policy, is sufficient and you
	//do not need to override this. For more on how sanitization works, see
	//the package doc. The statePropetyRef passed will always have the Index
	//properties set to -1, signifying that the returned policy applies to all
	//items in the Stack/Board.
	SanitizationPolicy(prop StatePropertyRef, groupMembership map[int]bool) Policy

	//If you have computed properties that you want to be included in your
	//JSON (for example, for use clientside), export them here by creating a
	//dictionary with their values.
	ComputedGlobalProperties(state ImmutableState) PropertyCollection
	ComputedPlayerProperties(player ImmutablePlayerState) PropertyCollection

	//Diagram should return a basic debug rendering of state in multi-line
	//ascii art. Useful for debugging. State.Diagram() will reach out to this
	//method.
	Diagram(s ImmutableState) string

	//SetManager configures which manager this delegate is in use with. A
	//given delegate can only be used by a single manager at a time.
	SetManager(manager *GameManager)

	//Manager returns the Manager that was set on this delegate.
	Manager() *GameManager
}

//PropertyCollection is just an alias for map[string]interface{}
type PropertyCollection map[string]interface{}

//Copy returns a shallow copy of PropertyCollection
func (p PropertyCollection) Copy() PropertyCollection {
	result := make(PropertyCollection, len(p))
	for key, val := range result {
		result[key] = val
	}
	return result
}
<commit_msg>Used a metaphor to explain the importance of GameDelegate. Part of #562.<commit_after>package boardgame

import (
	"github.com/jkomoros/boardgame/enum"
)

//GameDelegate is the place that various parts of the game lifecycle can be
//modified to support this particular game. Typically you embed
//base.GameDelegate in your won struct, and only override methods whose
//default behavior is incorrect for your game. Think of your GameDelegate like
//the brain that you plug into the robot body of a GameManager.
type GameDelegate interface {

	//Name is a string that defines the type of game this is. The name should
	//be unique and compact, and avoid any special characters other than "-"
	//or "_", since they will sometimes be used in a URL path. Good examples
	//are "tictactoe", "blackjack". Once configured, names should never change
	//over the lifetime of the gametype, since it will be persisted in
	//storage. Subclasses should override this. This must return the package
	//name that contains the game (e.g. "github.com/jkomoros/mygame" should
	//return "mygame"), since the package name and the delegate.Name() are
	//both used at different times in the system, since one can be determined
	//statically and the other only at run-time. NewGameManager will fail if
	//that is not true.
	Name() string

	//DisplayName is a string that defines the type of game this is in a way
	//appropriate for humans. The name should be unique but human readable. It
	//is purely for human consumption, and may change over time with no
	//adverse effects. Good examples are "Tic Tac Toe", "Blackjack".
	//Subclasses should override this.
	DisplayName() string

	//Description is a string that describes the game type in a descriptive
	//sentence. A reasonable value for "tictactoe" is "A classic game where
	//players compete to get three in a row"
	Description() string

	//ConfigureMoves will be called during creation of a GameManager in
	//NewGameManager. This is the time to install moves onto the manager by
	//returning a list of moves to install. Typically you use moves.Combine
	//and friends to organize your list of moves to install. If the moves you
	//add are illegal for any reason, NewGameManager will fail with an error.
	//By the time this is called. delegate.SetManager will already have been
	//called, so you'll have access to the manager via Manager().
	ConfigureMoves() []MoveConfig

	//ConfigureAgents will be called when creating a new GameManager. Emit the
	//agents you want to install.
	ConfigureAgents() []Agent

	//ConfigureDecks will be called when the GameManager is being booted up.
	//Each entry in the return value will be configured on the ComponentChest
	//that is being created.
	ConfigureDecks() map[string]*Deck

	//ConfigureEnums is called during set up of a new GameManager. Return the
	//set of enums you want to be associated with this GameManagaer's Chest.
	ConfigureEnums() *enum.Set

	//ConfigureConstants is called during set-up of a new GameManager. Return
	//the map of constants you want to create, which will be configured onto
	//the newly created chest via AddConstant. If any of the AddConstant calls
	//errors, the GameManager will fail to be set up. Constants are primarily
	//useful in two cases: first, when you want to have access to a constant
	//value client-side, and second, when you want to be able to use a
	//constant value in a tag-based struct inflater.
	ConfigureConstants() PropertyCollection

	//GameStateConstructor and PlayerStateConstructor are called to get an
	//instantiation of the concrete game/player structs that your package
	//defines. This is used both to create the initial state, but also to
	//inflate states from the database. These methods should always return the
	//underlying same type of struct when called. This means that if different
	//players have very different roles in a game, there might be many
	//properties that are not in use for any given player. The simple
	//properties (ints, bools, strings) should all be their zero-value.
	//Importantly, all Stacks, Timers, and Enums should be non- nil, because
	//an initialized struct contains information about things like MaxSize,
	//Size, and a reference to the deck they are affiliated with. GameManger
	//will automatically create and use StructInflaters for these types of
	//objects, allowing you to use tag-based configuration to automatically
	//inflate these properties. See the documentation for StructInflater for
	//more. Since these two methods are always required and always specific to
	//each game type, defaultGameDelegate does not implement them, as an extra
	//reminder that you must implement them yourself.
	GameStateConstructor() ConfigurableSubState
	//PlayerStateConstructor is similar to GameStateConstructor, but
	//playerIndex is the value that this PlayerState must return when its
	//PlayerIndex() is called.
	PlayerStateConstructor(player PlayerIndex) ConfigurablePlayerState

	//DynamicComponentValuesConstructor returns an empty
	//DynamicComponentValues for the given deck. If nil is returned, then the
	//components in that deck don't have any dynamic component state. This
	//method must always return the same underlying type of struct for the
	//same deck. Like GameStateConstructor and PlayerStateConstructor, the
	//engine will automatically create StructInflaters for these objects,
	//allowing you to use tag-based inflation of properties. See StructInflate
	//for more. If the returned object also implements the ComponentValues
	//interface, then SetContainingComponent will be called on the
	//DynamicComponent whenever one is created, with a reference back to the
	//component it's associated with.
	DynamicComponentValuesConstructor(deck *Deck) ConfigurableSubState

	//DistributeComponentToStarterStack is called during set up to establish
	//the Deck/Stack invariant that every component in the chest is placed in
	//precisely one Stack. Game will call this on each component in the Chest
	//in order. This is where the logic goes to make sure each Component goes
	//into its correct starter stack. You must return a non-nil Stack for each
	//call, after which the given Component will be inserted into
	//NextSlotIndex of that stack. If that is not the ordering you desire, you
	//can fix it up in FinishSetUp by using SwapComponents. If any errors are
	//returned, any nil Stacks are returned, or any returned stacks don't have
	//space for another component, NewGame will fail and return an error.
	//State and Component are only provided for reference; do not modify them.
	DistributeComponentToStarterStack(state ImmutableState, c Component) (ImmutableStack, error)

	//BeginSetup is a chance to modify the initial state object *before* the
	//components are distributed to it. It is also where the variant
	//configuration for your gametype will be passed (it will already have
	//been checked for legality and had all configure defaults set), although
	//you can also retrieve that at any time via game.Variant(). This is a
	//good place to configure state that will be necessary for you to make the
	//right decisions in DistributeComponentToStarterStack, or to transcribe
	//config information you were passed into properties on your gameState as
	//appropriate. If error is non-nil, Game setup will be aborted, with the
	//reasoning including the error message provided.
	BeginSetUp(state State, variant Variant) error

	//FinishSetUp is called during NewGame, *after* components have been
	//distributed to their StarterStack. This is the last chance to modify the
	//state before the game's initial state is considered final. For example,
	//if you have a card game this is where you'd make sure the starter draw
	//stacks are shuffled. If your game has multiple rounds, or if you don't
	//want the game to start with it already set-up (e.g. you want to show
	//animations of starter cards being dealt) then it's probably best to do
	//most of the logic in a SetUp phase. See the README for more. If error is
	//non-nil, Game setup will be aborted, with the reasoning including the
	//error message provided.
	FinishSetUp(state State) error

	//CheckGameFinished should return true if the game is finished, and who
	//the winners are. Called after every move is applied.
	CheckGameFinished(state ImmutableState) (finished bool, winners []PlayerIndex)

	//ProposeFixUpMove is called after a move has been applied. It may return
	//a FixUp move, which will be applied before any other moves are applied.
	//If it returns nil, we may take the next move off of the queue. FixUp
	//moves are useful for things like shuffling a discard deck back into a
	//draw deck, or other moves that are necessary to get the GameState back
	//into reasonable shape.
	ProposeFixUpMove(state ImmutableState) Move

	//DefaultNumPlayers returns the number of users that this game defaults to.
	//For example, for tictactoe, it will be 2. If 0 is provided to
	//manager.NewGame(), we wil use this value instead.
	DefaultNumPlayers() int

	//Min/MaxNumPlayers should return the min and max number of players,
	//respectively. The engine doesn't use this directly, instead looking at
	//LegalNumPlayers. Typically your LegalNumPlayers will check the given
	//number of players is between these two extremes.
	MinNumPlayers() int
	MaxNumPlayers() int

	//LegalNumPlayers will be consulted when a new game is created. It should
	//return true if the given number of players is legal, and false
	//otherwise. If this returns false, the NewGame will fail with an error.
	//Game creation will automatically reject a numPlayers that does not
	//result in at least one player existing. Generally this is simply
	//checking to make sure the number of players is between Min and Max
	//(inclusive), although some games could only allow, for example, even
	//numbers of players.
	LegalNumPlayers(numPlayers int) bool

	//Variants returns a VariantConfig, which describes the different
	//categories of configuration values and the legal values they may take
	//on. Ultimately your LegalVariant is the final arbiter of which variants
	//are legal; this method is mainly used so that user interfaces know which
	//variants to show to the user. In general you shouldn't call this, but
	//instead call gameManager.Variants() which will ensure your VariantConfig
	//is initalized and memoize the return result.
	Variants() VariantConfig

	//CurrentPlayerIndex returns the index of the "current" player--a notion
	//that is game specific (and sometimes inapplicable). If CurrentPlayer
	//doesn't make sense (perhaps the game never has a notion of current
	//player, or the type of round that we're in has no current player), this
	//should return ObserverPlayerIndex. The result of this method is used to
	//power state.CurrentPlayer.
	CurrentPlayerIndex(state ImmutableState) PlayerIndex

	//CurrentPhase returns the phase that the game state is currently in.
	//Phase is a formalized convention used in moves.Default to make it easier
	//to write fix-up moves that only apply in certain phases, like SetUp. The
	//return result is primarily used in moves.Default to check whether it is
	//one of the phases in a give Move's LegalPhases. See moves.Default for
	//more information.
	CurrentPhase(state ImmutableState) int

	//PhaseEnum returns the enum for game phases (the return values of
	//CurrentPhase are expected to be valid enums within that enum). If this
	//returns a non-nil enums.TreeEnum, then the state will not be able to be
	//saved if CurrentPhase() returns a value that is not a leaf-node.
	PhaseEnum() enum.Enum

	//SanitizationPolicy is consulted when sanitizing states. It is called for
	//each prop in the state, including the set of groups that this player is
	//a mamber of. In practice the default behavior of defaultGameDelegate,
	//which uses struct tags to figure out the policy, is sufficient and you
	//do not need to override this. For more on how sanitization works, see
	//the package doc. The statePropetyRef passed will always have the Index
	//properties set to -1, signifying that the returned policy applies to all
	//items in the Stack/Board.
	SanitizationPolicy(prop StatePropertyRef, groupMembership map[int]bool) Policy

	//If you have computed properties that you want to be included in your
	//JSON (for example, for use clientside), export them here by creating a
	//dictionary with their values.
	ComputedGlobalProperties(state ImmutableState) PropertyCollection
	ComputedPlayerProperties(player ImmutablePlayerState) PropertyCollection

	//Diagram should return a basic debug rendering of state in multi-line
	//ascii art. Useful for debugging. State.Diagram() will reach out to this
	//method.
	Diagram(s ImmutableState) string

	//SetManager configures which manager this delegate is in use with. A
	//given delegate can only be used by a single manager at a time.
	SetManager(manager *GameManager)

	//Manager returns the Manager that was set on this delegate.
	Manager() *GameManager
}

//PropertyCollection is just an alias for map[string]interface{}
type PropertyCollection map[string]interface{}

//Copy returns a shallow copy of PropertyCollection
func (p PropertyCollection) Copy() PropertyCollection {
	result := make(PropertyCollection, len(p))
	for key, val := range result {
		result[key] = val
	}
	return result
}
<|endoftext|>


<commit_before>package garagepi_test

import (
	"errors"
	"io"
	"net/http"
	"net/url"
	"strconv"

	"bytes"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/robdimsdale/garagepi"
	garagepi_fakes "github.com/robdimsdale/garagepi/fakes"
)

var (
	fakeLogger         *garagepi_fakes.FakeLogger
	fakeHttpHelper     *garagepi_fakes.FakeHttpHelper
	fakeOsHelper       *garagepi_fakes.FakeOsHelper
	fakeFsHelper       *garagepi_fakes.FakeFsHelper
	fakeResponseWriter *garagepi_fakes.FakeResponseWriter
	dummyRequest       *http.Request
)

func tostr(u uint) string {
	return strconv.FormatUint(uint64(u), 10)
}

func verifyRedirectToHomepage() {
	Expect(fakeHttpHelper.RedirectToHomepageCallCount()).To(Equal(1))
	w, r := fakeHttpHelper.RedirectToHomepageArgsForCall(0)
	Expect(w).To(Equal(fakeResponseWriter))
	Expect(r).To(Equal(dummyRequest))
}

var _ = Describe("Garagepi", func() {

	webcamHost := "webcamHost"
	webcamPort := uint(12345)

	gpioDoorPin := uint(0)
	gpioLightPin := uint(8)
	gpioExecutable := "gpio"

	executorConfig := garagepi.ExecutorConfig{
		WebcamHost:     webcamHost,
		WebcamPort:     webcamPort,
		GpioDoorPin:    gpioDoorPin,
		GpioLightPin:   gpioLightPin,
		GpioExecutable: gpioExecutable,
	}

	var executor *garagepi.Executor
	BeforeEach(func() {
		fakeLogger = new(garagepi_fakes.FakeLogger)
		fakeHttpHelper = new(garagepi_fakes.FakeHttpHelper)
		fakeOsHelper = new(garagepi_fakes.FakeOsHelper)
		fakeFsHelper = new(garagepi_fakes.FakeFsHelper)

		fakeResponseWriter = new(garagepi_fakes.FakeResponseWriter)
		dummyRequest = new(http.Request)

		executor = garagepi.NewExecutor(
			fakeLogger,
			fakeOsHelper,
			fakeFsHelper,
			fakeHttpHelper,
			executorConfig,
		)
	})

	Describe("Homepage Handling", func() {
		Context("When reading the homepage template is successful", func() {
			contents := []byte("contents")

			BeforeEach(func() {
				fakeFsHelper.GetHomepageTemplateContentsReturns(contents, nil)
			})

			It("Should write the contents of the homepage template to the response writer", func() {
				executor.HomepageHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeFsHelper.GetHomepageTemplateContentsCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal(contents))
			})
		})

		Context("When reading the homepage template fails with error", func() {
			BeforeEach(func() {
				fakeFsHelper.GetHomepageTemplateContentsReturns(nil, errors.New("Failed to read contents"))
			})

			execution := func() {
				executor.HomepageHandler(fakeResponseWriter, dummyRequest)
			}

			It("Should panic", func() {
				Expect(execution).Should(Panic())
			})
		})
	})

	Describe("Webcam handling", func() {
		Context("When obtaining a webcam image is successful", func() {
			contents := []byte("webcamImage")
			BeforeEach(func() {
				dummyResponse := new(http.Response)
				dummyResponse.Body = nopCloser{bytes.NewReader(contents)}
				fakeHttpHelper.GetReturns(dummyResponse, nil)
			})

			It("Should write the contents of the response to the response writer", func() {
				executor.WebcamHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal(contents))
			})
		})

		Context("When obtaining a webcam image fails with error", func() {
			BeforeEach(func() {
				fakeHttpHelper.GetReturns(nil, errors.New("Failed to GET url"))
			})

			It("Should write nothing to the response writer and return", func() {
				executor.WebcamHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(0))
			})
		})

		Context("When reading the webcam image fails with error", func() {
			BeforeEach(func() {
				dummyResponse := new(http.Response)
				dummyResponse.Body = errCloser{bytes.NewReader([]byte{})}
				fakeHttpHelper.GetReturns(dummyResponse, nil)
			})

			It("Should write nothing to the response writer and return", func() {
				executor.WebcamHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(0))
			})
		})
	})

	Describe("Door-toggle handling", func() {
		verifyGpioWriteHighFirstThenWriteLow := func() {
			gpioCalls := 0

			args := make([][]string, 2)

			for i := 0; i < fakeOsHelper.ExecCallCount(); i++ {
				executable, curArgs := fakeOsHelper.ExecArgsForCall(i)
				if executable != gpioExecutable {
					continue
				}
				if gpioCalls == 0 {
					gpioCalls++
					args[0] = curArgs
				} else if gpioCalls == 1 {
					gpioCalls++
					args[1] = curArgs
				} else {
					gpioCalls++
				}
			}
			Expect(gpioCalls).To(Equal(2))

			Expect(args[0]).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioDoorPin), garagepi.GpioHighState}))
			Expect(args[1]).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioDoorPin), garagepi.GpioLowState}))
		}

		verifyGpioWriteHighThenNoFurtherGpioCalls := func() {
			gpioCalls := 0

			args := make([]string, 1)

			for i := 0; i < fakeOsHelper.ExecCallCount(); i++ {
				executable, curArgs := fakeOsHelper.ExecArgsForCall(i)
				if executable != gpioExecutable {
					continue
				}
				if gpioCalls == 0 {
					gpioCalls++
					args = curArgs
				} else {
					gpioCalls++
				}
			}
			Expect(gpioCalls).To(Equal(1))
			Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioDoorPin), garagepi.GpioHighState}))
		}

		Context("When executing "+gpioExecutable+" commands return sucessfully", func() {
			It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioDoorPin)+", sleep, and write "+garagepi.GpioLowState+" to gpio "+tostr(gpioDoorPin), func() {
				executor.ToggleDoorHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeOsHelper.SleepArgsForCall(0)).To(Equal(garagepi.SleepTime))
				verifyGpioWriteHighFirstThenWriteLow()
			})

			It("Should return 'door toggled'", func() {
				executor.ToggleDoorHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("door toggled")))
			})
		})

		Context("When executing the first "+gpioExecutable+" command returns with errors", func() {
			BeforeEach(func() {
				fakeOsHelper.ExecStub = func(executable string, _ ...string) (string, error) {
					if executable == gpioExecutable {
						return "", errors.New(garagepi.GpioWriteCommand + " " + gpioExecutable + "error")
					}
					return "", nil
				}
			})

			It("Should not sleep or execute further gpio commands", func() {
				executor.ToggleDoorHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeOsHelper.SleepCallCount()).To(Equal(0))
				verifyGpioWriteHighThenNoFurtherGpioCalls()
			})

			It("Should return 'error - door not toggled'", func() {
				executor.ToggleDoorHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("error - door not toggled")))
			})
		})
	})

	Describe("Light handling", func() {
		Describe("Posting to /light without state information", func() {
			BeforeEach(func() {
				u, err := url.Parse("/?state")
				Expect(err).ShouldNot(HaveOccurred())
				dummyRequest.URL = u
			})
			It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioLightPin), func() {
				executor.LightHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
				executable, args := fakeOsHelper.ExecArgsForCall(0)
				Expect(executable).To(Equal("gpio"))
				Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioHighState}))
			})

			It("Should return 'light on'", func() {
				executor.LightHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("light on")))
			})
		})

		Describe("Posting to /light with incorrect state information", func() {
			BeforeEach(func() {
				u, err := url.Parse("/?state=somefakevalue")
				Expect(err).ShouldNot(HaveOccurred())
				dummyRequest.URL = u
			})
			It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioLightPin), func() {
				executor.LightHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
				executable, args := fakeOsHelper.ExecArgsForCall(0)
				Expect(executable).To(Equal("gpio"))
				Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioHighState}))
			})

			It("Should return 'light on'", func() {
				executor.LightHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("light on")))
			})
		})

		Describe("Turning light on", func() {
			BeforeEach(func() {
				u, err := url.Parse("/?state=on")
				Expect(err).ShouldNot(HaveOccurred())
				dummyRequest.URL = u
			})

			Context("When turning on light commands returns with error", func() {
				BeforeEach(func() {

					fakeOsHelper.ExecStub = func(executable string, _ ...string) (string, error) {
						if executable == gpioExecutable {
							return "", errors.New(garagepi.GpioWriteCommand + " " + gpioExecutable + "error")
						}
						return "", nil
					}
				})
				It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioLightPin), func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
					executable, args := fakeOsHelper.ExecArgsForCall(0)
					Expect(executable).To(Equal(gpioExecutable))
					Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioHighState}))
				})

				It("Should return 'error - light state unchanged'", func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
					Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("error - light state unchanged")))
				})
			})

			Context("When turning on light command returns sucessfully", func() {
				It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioLightPin), func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
					executable, args := fakeOsHelper.ExecArgsForCall(0)
					Expect(executable).To(Equal("gpio"))
					Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioHighState}))
				})

				It("Should return 'light on'", func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
					Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("light on")))
				})
			})
		})
		Describe("Turning light off", func() {
			BeforeEach(func() {
				u, err := url.Parse("/?state=off")
				Expect(err).ShouldNot(HaveOccurred())
				dummyRequest.URL = u
			})

			Context("When turning off light command returns with error", func() {
				BeforeEach(func() {

					fakeOsHelper.ExecStub = func(executable string, _ ...string) (string, error) {
						if executable == gpioExecutable {
							return "", errors.New(gpioExecutable + " " + garagepi.GpioWriteCommand + "error")
						}
						return "", nil
					}
				})
				It("Should write "+garagepi.GpioLowState+" to gpio "+tostr(gpioLightPin), func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
					executable, args := fakeOsHelper.ExecArgsForCall(0)
					Expect(executable).To(Equal(gpioExecutable))
					Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioLowState}))
				})

				It("Should return 'light state unchanged'", func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
					Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("error - light state unchanged")))
				})
			})

			Context("When turning off light command return sucessfully", func() {
				It("Should write "+garagepi.GpioLowState+" to gpio "+tostr(gpioLightPin), func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
					executable, args := fakeOsHelper.ExecArgsForCall(0)
					Expect(executable).To(Equal("gpio"))
					Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioLowState}))
				})

				It("Should return 'light off'", func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
					Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("light off")))
				})
			})
		})

	})
})

type nopCloser struct {
	io.Reader
}

func (n nopCloser) Close() error {
	return nil
}

type errCloser struct {
	io.Reader
}

func (e errCloser) Read(p []byte) (n int, err error) {
	return 0, errors.New("ReadError")
}

func (e errCloser) Close() error {
	return nil
}
<commit_msg>Remove unused 'verifyRedirectToHomepage method from garagepi_test.go<commit_after>package garagepi_test

import (
	"errors"
	"io"
	"net/http"
	"net/url"
	"strconv"

	"bytes"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/robdimsdale/garagepi"
	garagepi_fakes "github.com/robdimsdale/garagepi/fakes"
)

var (
	fakeLogger         *garagepi_fakes.FakeLogger
	fakeHttpHelper     *garagepi_fakes.FakeHttpHelper
	fakeOsHelper       *garagepi_fakes.FakeOsHelper
	fakeFsHelper       *garagepi_fakes.FakeFsHelper
	fakeResponseWriter *garagepi_fakes.FakeResponseWriter
	dummyRequest       *http.Request
)

func tostr(u uint) string {
	return strconv.FormatUint(uint64(u), 10)
}

var _ = Describe("Garagepi", func() {

	webcamHost := "webcamHost"
	webcamPort := uint(12345)

	gpioDoorPin := uint(0)
	gpioLightPin := uint(8)
	gpioExecutable := "gpio"

	executorConfig := garagepi.ExecutorConfig{
		WebcamHost:     webcamHost,
		WebcamPort:     webcamPort,
		GpioDoorPin:    gpioDoorPin,
		GpioLightPin:   gpioLightPin,
		GpioExecutable: gpioExecutable,
	}

	var executor *garagepi.Executor
	BeforeEach(func() {
		fakeLogger = new(garagepi_fakes.FakeLogger)
		fakeHttpHelper = new(garagepi_fakes.FakeHttpHelper)
		fakeOsHelper = new(garagepi_fakes.FakeOsHelper)
		fakeFsHelper = new(garagepi_fakes.FakeFsHelper)

		fakeResponseWriter = new(garagepi_fakes.FakeResponseWriter)
		dummyRequest = new(http.Request)

		executor = garagepi.NewExecutor(
			fakeLogger,
			fakeOsHelper,
			fakeFsHelper,
			fakeHttpHelper,
			executorConfig,
		)
	})

	Describe("Homepage Handling", func() {
		Context("When reading the homepage template is successful", func() {
			contents := []byte("contents")

			BeforeEach(func() {
				fakeFsHelper.GetHomepageTemplateContentsReturns(contents, nil)
			})

			It("Should write the contents of the homepage template to the response writer", func() {
				executor.HomepageHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeFsHelper.GetHomepageTemplateContentsCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal(contents))
			})
		})

		Context("When reading the homepage template fails with error", func() {
			BeforeEach(func() {
				fakeFsHelper.GetHomepageTemplateContentsReturns(nil, errors.New("Failed to read contents"))
			})

			execution := func() {
				executor.HomepageHandler(fakeResponseWriter, dummyRequest)
			}

			It("Should panic", func() {
				Expect(execution).Should(Panic())
			})
		})
	})

	Describe("Webcam handling", func() {
		Context("When obtaining a webcam image is successful", func() {
			contents := []byte("webcamImage")
			BeforeEach(func() {
				dummyResponse := new(http.Response)
				dummyResponse.Body = nopCloser{bytes.NewReader(contents)}
				fakeHttpHelper.GetReturns(dummyResponse, nil)
			})

			It("Should write the contents of the response to the response writer", func() {
				executor.WebcamHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal(contents))
			})
		})

		Context("When obtaining a webcam image fails with error", func() {
			BeforeEach(func() {
				fakeHttpHelper.GetReturns(nil, errors.New("Failed to GET url"))
			})

			It("Should write nothing to the response writer and return", func() {
				executor.WebcamHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(0))
			})
		})

		Context("When reading the webcam image fails with error", func() {
			BeforeEach(func() {
				dummyResponse := new(http.Response)
				dummyResponse.Body = errCloser{bytes.NewReader([]byte{})}
				fakeHttpHelper.GetReturns(dummyResponse, nil)
			})

			It("Should write nothing to the response writer and return", func() {
				executor.WebcamHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(0))
			})
		})
	})

	Describe("Door-toggle handling", func() {
		verifyGpioWriteHighFirstThenWriteLow := func() {
			gpioCalls := 0

			args := make([][]string, 2)

			for i := 0; i < fakeOsHelper.ExecCallCount(); i++ {
				executable, curArgs := fakeOsHelper.ExecArgsForCall(i)
				if executable != gpioExecutable {
					continue
				}
				if gpioCalls == 0 {
					gpioCalls++
					args[0] = curArgs
				} else if gpioCalls == 1 {
					gpioCalls++
					args[1] = curArgs
				} else {
					gpioCalls++
				}
			}
			Expect(gpioCalls).To(Equal(2))

			Expect(args[0]).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioDoorPin), garagepi.GpioHighState}))
			Expect(args[1]).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioDoorPin), garagepi.GpioLowState}))
		}

		verifyGpioWriteHighThenNoFurtherGpioCalls := func() {
			gpioCalls := 0

			args := make([]string, 1)

			for i := 0; i < fakeOsHelper.ExecCallCount(); i++ {
				executable, curArgs := fakeOsHelper.ExecArgsForCall(i)
				if executable != gpioExecutable {
					continue
				}
				if gpioCalls == 0 {
					gpioCalls++
					args = curArgs
				} else {
					gpioCalls++
				}
			}
			Expect(gpioCalls).To(Equal(1))
			Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioDoorPin), garagepi.GpioHighState}))
		}

		Context("When executing "+gpioExecutable+" commands return sucessfully", func() {
			It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioDoorPin)+", sleep, and write "+garagepi.GpioLowState+" to gpio "+tostr(gpioDoorPin), func() {
				executor.ToggleDoorHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeOsHelper.SleepArgsForCall(0)).To(Equal(garagepi.SleepTime))
				verifyGpioWriteHighFirstThenWriteLow()
			})

			It("Should return 'door toggled'", func() {
				executor.ToggleDoorHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("door toggled")))
			})
		})

		Context("When executing the first "+gpioExecutable+" command returns with errors", func() {
			BeforeEach(func() {
				fakeOsHelper.ExecStub = func(executable string, _ ...string) (string, error) {
					if executable == gpioExecutable {
						return "", errors.New(garagepi.GpioWriteCommand + " " + gpioExecutable + "error")
					}
					return "", nil
				}
			})

			It("Should not sleep or execute further gpio commands", func() {
				executor.ToggleDoorHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeOsHelper.SleepCallCount()).To(Equal(0))
				verifyGpioWriteHighThenNoFurtherGpioCalls()
			})

			It("Should return 'error - door not toggled'", func() {
				executor.ToggleDoorHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("error - door not toggled")))
			})
		})
	})

	Describe("Light handling", func() {
		Describe("Posting to /light without state information", func() {
			BeforeEach(func() {
				u, err := url.Parse("/?state")
				Expect(err).ShouldNot(HaveOccurred())
				dummyRequest.URL = u
			})
			It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioLightPin), func() {
				executor.LightHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
				executable, args := fakeOsHelper.ExecArgsForCall(0)
				Expect(executable).To(Equal("gpio"))
				Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioHighState}))
			})

			It("Should return 'light on'", func() {
				executor.LightHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("light on")))
			})
		})

		Describe("Posting to /light with incorrect state information", func() {
			BeforeEach(func() {
				u, err := url.Parse("/?state=somefakevalue")
				Expect(err).ShouldNot(HaveOccurred())
				dummyRequest.URL = u
			})
			It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioLightPin), func() {
				executor.LightHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
				executable, args := fakeOsHelper.ExecArgsForCall(0)
				Expect(executable).To(Equal("gpio"))
				Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioHighState}))
			})

			It("Should return 'light on'", func() {
				executor.LightHandler(fakeResponseWriter, dummyRequest)
				Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
				Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("light on")))
			})
		})

		Describe("Turning light on", func() {
			BeforeEach(func() {
				u, err := url.Parse("/?state=on")
				Expect(err).ShouldNot(HaveOccurred())
				dummyRequest.URL = u
			})

			Context("When turning on light commands returns with error", func() {
				BeforeEach(func() {

					fakeOsHelper.ExecStub = func(executable string, _ ...string) (string, error) {
						if executable == gpioExecutable {
							return "", errors.New(garagepi.GpioWriteCommand + " " + gpioExecutable + "error")
						}
						return "", nil
					}
				})
				It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioLightPin), func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
					executable, args := fakeOsHelper.ExecArgsForCall(0)
					Expect(executable).To(Equal(gpioExecutable))
					Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioHighState}))
				})

				It("Should return 'error - light state unchanged'", func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
					Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("error - light state unchanged")))
				})
			})

			Context("When turning on light command returns sucessfully", func() {
				It("Should write "+garagepi.GpioHighState+" to gpio "+tostr(gpioLightPin), func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
					executable, args := fakeOsHelper.ExecArgsForCall(0)
					Expect(executable).To(Equal("gpio"))
					Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioHighState}))
				})

				It("Should return 'light on'", func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
					Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("light on")))
				})
			})
		})
		Describe("Turning light off", func() {
			BeforeEach(func() {
				u, err := url.Parse("/?state=off")
				Expect(err).ShouldNot(HaveOccurred())
				dummyRequest.URL = u
			})

			Context("When turning off light command returns with error", func() {
				BeforeEach(func() {

					fakeOsHelper.ExecStub = func(executable string, _ ...string) (string, error) {
						if executable == gpioExecutable {
							return "", errors.New(gpioExecutable + " " + garagepi.GpioWriteCommand + "error")
						}
						return "", nil
					}
				})
				It("Should write "+garagepi.GpioLowState+" to gpio "+tostr(gpioLightPin), func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
					executable, args := fakeOsHelper.ExecArgsForCall(0)
					Expect(executable).To(Equal(gpioExecutable))
					Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioLowState}))
				})

				It("Should return 'light state unchanged'", func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
					Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("error - light state unchanged")))
				})
			})

			Context("When turning off light command return sucessfully", func() {
				It("Should write "+garagepi.GpioLowState+" to gpio "+tostr(gpioLightPin), func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeOsHelper.ExecCallCount()).To(Equal(1))
					executable, args := fakeOsHelper.ExecArgsForCall(0)
					Expect(executable).To(Equal("gpio"))
					Expect(args).To(Equal([]string{garagepi.GpioWriteCommand, tostr(gpioLightPin), garagepi.GpioLowState}))
				})

				It("Should return 'light off'", func() {
					executor.LightHandler(fakeResponseWriter, dummyRequest)
					Expect(fakeResponseWriter.WriteCallCount()).To(Equal(1))
					Expect(fakeResponseWriter.WriteArgsForCall(0)).To(Equal([]byte("light off")))
				})
			})
		})

	})
})

type nopCloser struct {
	io.Reader
}

func (n nopCloser) Close() error {
	return nil
}

type errCloser struct {
	io.Reader
}

func (e errCloser) Read(p []byte) (n int, err error) {
	return 0, errors.New("ReadError")
}

func (e errCloser) Close() error {
	return nil
}
<|endoftext|>