<commit_before>// Copyright 2017 CoreOS, Inc.
//
// 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 blackbox

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

	config "github.com/coreos/ignition/config/v2_3_experimental"
	"github.com/coreos/ignition/tests/register"
	"github.com/coreos/ignition/tests/types"

	// Register the tests
	_ "github.com/coreos/ignition/tests/registry"

	// UUID generation tool
	"github.com/pborman/uuid"
)

var (
	// testTimeout controls how long a given test is allowed to run before being
	// cancelled.
	testTimeout = time.Second * 30
)

func TestMain(m *testing.M) {
	httpServer := &HTTPServer{}
	httpServer.Start()
	tftpServer := &TFTPServer{}
	tftpServer.Start()

	os.Exit(m.Run())
}

func TestIgnitionBlackBox(t *testing.T) {
	for _, test := range register.Tests[register.PositiveTest] {
		test := test
		t.Run(test.Name, func(t *testing.T) {
			t.Parallel()
			err := outer(t, test, false)
			if err != nil {
				t.Error(err)
			}
		})
	}
}

func TestIgnitionBlackBoxNegative(t *testing.T) {
	for _, test := range register.Tests[register.NegativeTest] {
		test := test
		t.Run(test.Name, func(t *testing.T) {
			t.Parallel()
			err := outer(t, test, true)
			if err != nil {
				t.Error(err)
			}
		})
	}
}

func outer(t *testing.T, test types.Test, negativeTests bool) error {
	t.Log(test.Name)

	err := test.ReplaceAllUUIDVars()
	if err != nil {
		return err
	}

	ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(testTimeout))
	defer cancelFunc()

	tmpDirectory, err := ioutil.TempDir("/var/tmp", "ignition-blackbox-")
	if err != nil {
		return fmt.Errorf("failed to create a temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDirectory)
	// the tmpDirectory must be 0755 or the tests will fail as the tool will
	// not have permissions to perform some actions in the mounted folders
	err = os.Chmod(tmpDirectory, 0755)
	if err != nil {
		return fmt.Errorf("failed to change mode of temp dir: %v", err)
	}

	oemLookasideDir := filepath.Join(tmpDirectory, "oem-lookaside")
	systemConfigDir := filepath.Join(tmpDirectory, "system")
	var rootLocation string

	// Setup
	err = createFilesFromSlice(t, oemLookasideDir, test.OEMLookasideFiles)
	// Defer before the error handling because the createFilesFromSlice function
	// can fail after partially-creating things
	defer os.RemoveAll(oemLookasideDir)
	if err != nil {
		return err
	}
	err = createFilesFromSlice(t, systemConfigDir, test.SystemDirFiles)
	defer os.RemoveAll(systemConfigDir)
	if err != nil {
		return err
	}
	for i, disk := range test.In {
		// Set image file path
		disk.ImageFile = filepath.Join(tmpDirectory, fmt.Sprintf("hd%d", i))
		test.Out[i].ImageFile = disk.ImageFile

		// There may be more partitions created by Ignition, so look at the
		// expected output instead of the input to determine image size
		imageSize := test.Out[i].CalculateImageSize()
		if inSize := disk.CalculateImageSize(); inSize > imageSize {
			imageSize = inSize
		}

		// Finish data setup
		for _, part := range disk.Partitions {
			if part.GUID == "" {
				part.GUID = uuid.New()
				if err != nil {
					return err
				}
			}
			err := updateTypeGUID(part)
			if err != nil {
				return err
			}
		}

		disk.SetOffsets()
		for _, part := range test.Out[i].Partitions {
			err := updateTypeGUID(part)
			if err != nil {
				return err
			}
		}
		test.Out[i].SetOffsets()

		if err = setupDisk(t, ctx, &disk, i, imageSize, tmpDirectory); err != nil {
			return err
		}

		// Creation
		// Move value into the local scope, because disk.ImageFile and device
		// will change by the time this runs
		imageFile := disk.ImageFile
		device := disk.Device
		defer func() {
			if err := os.Remove(imageFile); err != nil {
				t.Errorf("couldn't remove %s: %v", imageFile, err)
			}
		}()
		defer func() {
			if err := destroyDevice(t, device); err != nil {
				t.Errorf("couldn't destroy device: %v", err)
			}
		}()

		test.Out[i].Device = disk.Device

		rootMounted, err := mountRootPartition(t, ctx, disk.Partitions)
		partitions := disk.Partitions
		defer func() {
			if err := unmountRootPartition(t, partitions); err != nil {
				t.Errorf("couldn't unmount root partition: %v", err)
			}
		}()
		if err != nil {
			return err
		}

		if rootMounted && strings.Contains(test.Config, "passwd") {
			if err = prepareRootPartitionForPasswd(t, ctx, disk.Partitions); err != nil {
				return err
			}
		}

		if err = mountPartitions(t, ctx, disk.Partitions); err != nil {
			// mountPartitions may have partially succeded, so at least try to
			// unmount things.
			unmountPartitions(t, disk.Partitions)
			return err
		}

		err = createFilesForPartitions(t, disk.Partitions)
		// unmount even if createFilesForPartitions failed
		errUnmount := unmountPartitions(t, disk.Partitions)
		if err != nil {
			if errUnmount != nil {
				t.Errorf("couldn't unmount partitions: %v", errUnmount)
			}
			return err
		} else if errUnmount != nil {
			return errUnmount
		}

		// Mount device name substitution
		for _, d := range test.MntDevices {
			device := pickPartition(t, disk.Device, disk.Partitions, d.Label)
			// The device may not be on this disk, if it's not found here let's
			// assume we'll find it on another one and keep going
			if device != "" {
				test.Config = strings.Replace(test.Config, d.Substitution, device, -1)
			}
		}

		// Replace any instance of $disk<num> with the actual loop device
		// that got assigned to it
		test.Config = strings.Replace(test.Config, fmt.Sprintf("$disk%d", i), disk.Device, -1)

		if rootLocation == "" {
			rootLocation = getRootLocation(disk.Partitions)
		}
	}

	// Validation and cleanup deferral
	for i, disk := range test.Out {
		// Update out structure with mount points & devices
		setExpectedPartitionsDrive(test.In[i].Partitions, disk.Partitions)
	}

	if rootLocation == "" {
		return fmt.Errorf("ROOT filesystem not found! A partition labeled ROOT is requred")
	}

	// Let's make sure that all of the devices we needed to substitute names in
	// for were found
	for _, d := range test.MntDevices {
		if strings.Contains(test.Config, d.Substitution) {
			return fmt.Errorf("Didn't find a drive with label: %s", d.Substitution)
		}
	}

	// If we're not expecting the config to be bad, make sure it passes
	// validation.
	if !test.ConfigShouldBeBad {
		_, rpt, err := config.Parse([]byte(test.Config))
		if rpt.IsFatal() {
			return fmt.Errorf("test has bad config: %s", rpt.String())
		}
		if err != nil {
			return fmt.Errorf("error parsing config: %v", err)
		}
	}

	// Ignition config
	if err := ioutil.WriteFile(filepath.Join(tmpDirectory, "config.ign"), []byte(test.Config), 0666); err != nil {
		return fmt.Errorf("error writing config: %v", err)
	}

	// Ignition
	appendEnv := []string{
		"IGNITION_OEM_DEVICE=" + test.In[0].Partitions.GetPartition("OEM").Device,
		"IGNITION_OEM_LOOKASIDE_DIR=" + oemLookasideDir,
		"IGNITION_SYSTEM_CONFIG_DIR=" + systemConfigDir,
	}
	disksErr := runIgnition(t, ctx, "disks", rootLocation, tmpDirectory, appendEnv)
	if !negativeTests && disksErr != nil {
		return disksErr
	}
	filesErr := runIgnition(t, ctx, "files", rootLocation, tmpDirectory, appendEnv)
	if !negativeTests && filesErr != nil {
		return filesErr
	}
	if negativeTests && disksErr == nil && filesErr == nil {
		return fmt.Errorf("Expected failure and ignition succeeded")
	}

	for _, disk := range test.Out {
		if !negativeTests {
			err = validateDisk(t, disk)
			if err != nil {
				return err
			}
			err = validateFilesystems(t, disk.Partitions)
			if err != nil {
				return err
			}
			validateFilesDirectoriesAndLinks(t, disk.Partitions)
		}
	}
	return nil
}
<commit_msg>tests: add a cancelation mechanism<commit_after>// Copyright 2017 CoreOS, Inc.
//
// 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 blackbox

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"testing"
	"time"

	config "github.com/coreos/ignition/config/v2_3_experimental"
	"github.com/coreos/ignition/tests/register"
	"github.com/coreos/ignition/tests/types"

	// Register the tests
	_ "github.com/coreos/ignition/tests/registry"

	// UUID generation tool
	"github.com/pborman/uuid"
)

var (
	// testTimeout controls how long a given test is allowed to run before being
	// cancelled.
	testTimeout = time.Second * 30
	// somewhat of an abuse of contexts but go's got our hands tied
	killContext = context.TODO()
)

func TestMain(m *testing.M) {
	httpServer := &HTTPServer{}
	httpServer.Start()
	tftpServer := &TFTPServer{}
	tftpServer.Start()

	interruptChan := make(chan os.Signal, 3)
	signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM)
	tmp, killCancel := context.WithCancel(context.Background())
	killContext = tmp
	go func() {
		for {
			sig := <-interruptChan
			switch sig {
			case os.Interrupt, syscall.SIGTERM:
				killCancel()
			}
		}
	}()

	os.Exit(m.Run())
}

func TestIgnitionBlackBox(t *testing.T) {
	for _, test := range register.Tests[register.PositiveTest] {
		test := test
		t.Run(test.Name, func(t *testing.T) {
			if killContext.Err() != nil {
				t.SkipNow()
			}
			t.Parallel()
			err := outer(t, test, false)
			if err != nil {
				t.Error(err)
			}
		})
	}
}

func TestIgnitionBlackBoxNegative(t *testing.T) {
	for _, test := range register.Tests[register.NegativeTest] {
		test := test
		t.Run(test.Name, func(t *testing.T) {
			if killContext.Err() != nil {
				t.SkipNow()
			}
			t.Parallel()
			err := outer(t, test, true)
			if err != nil {
				t.Error(err)
			}
		})
	}
}

func outer(t *testing.T, test types.Test, negativeTests bool) error {
	t.Log(test.Name)

	err := test.ReplaceAllUUIDVars()
	if err != nil {
		return err
	}

	ctx, cancelFunc := context.WithDeadline(killContext, time.Now().Add(testTimeout))
	defer cancelFunc()

	tmpDirectory, err := ioutil.TempDir("/var/tmp", "ignition-blackbox-")
	if err != nil {
		return fmt.Errorf("failed to create a temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDirectory)
	// the tmpDirectory must be 0755 or the tests will fail as the tool will
	// not have permissions to perform some actions in the mounted folders
	err = os.Chmod(tmpDirectory, 0755)
	if err != nil {
		return fmt.Errorf("failed to change mode of temp dir: %v", err)
	}

	oemLookasideDir := filepath.Join(tmpDirectory, "oem-lookaside")
	systemConfigDir := filepath.Join(tmpDirectory, "system")
	var rootLocation string

	// Setup
	err = createFilesFromSlice(t, oemLookasideDir, test.OEMLookasideFiles)
	// Defer before the error handling because the createFilesFromSlice function
	// can fail after partially-creating things
	defer os.RemoveAll(oemLookasideDir)
	if err != nil {
		return err
	}
	err = createFilesFromSlice(t, systemConfigDir, test.SystemDirFiles)
	defer os.RemoveAll(systemConfigDir)
	if err != nil {
		return err
	}
	for i, disk := range test.In {
		// Set image file path
		disk.ImageFile = filepath.Join(tmpDirectory, fmt.Sprintf("hd%d", i))
		test.Out[i].ImageFile = disk.ImageFile

		// There may be more partitions created by Ignition, so look at the
		// expected output instead of the input to determine image size
		imageSize := test.Out[i].CalculateImageSize()
		if inSize := disk.CalculateImageSize(); inSize > imageSize {
			imageSize = inSize
		}

		// Finish data setup
		for _, part := range disk.Partitions {
			if part.GUID == "" {
				part.GUID = uuid.New()
				if err != nil {
					return err
				}
			}
			err := updateTypeGUID(part)
			if err != nil {
				return err
			}
		}

		disk.SetOffsets()
		for _, part := range test.Out[i].Partitions {
			err := updateTypeGUID(part)
			if err != nil {
				return err
			}
		}
		test.Out[i].SetOffsets()

		if err = setupDisk(t, ctx, &disk, i, imageSize, tmpDirectory); err != nil {
			return err
		}

		// Creation
		// Move value into the local scope, because disk.ImageFile and device
		// will change by the time this runs
		imageFile := disk.ImageFile
		device := disk.Device
		defer func() {
			if err := os.Remove(imageFile); err != nil {
				t.Errorf("couldn't remove %s: %v", imageFile, err)
			}
		}()
		defer func() {
			if err := destroyDevice(t, device); err != nil {
				t.Errorf("couldn't destroy device: %v", err)
			}
		}()

		test.Out[i].Device = disk.Device

		rootMounted, err := mountRootPartition(t, ctx, disk.Partitions)
		partitions := disk.Partitions
		defer func() {
			if err := unmountRootPartition(t, partitions); err != nil {
				t.Errorf("couldn't unmount root partition: %v", err)
			}
		}()
		if err != nil {
			return err
		}

		if rootMounted && strings.Contains(test.Config, "passwd") {
			if err = prepareRootPartitionForPasswd(t, ctx, disk.Partitions); err != nil {
				return err
			}
		}

		if err = mountPartitions(t, ctx, disk.Partitions); err != nil {
			// mountPartitions may have partially succeded, so at least try to
			// unmount things.
			unmountPartitions(t, disk.Partitions)
			return err
		}

		err = createFilesForPartitions(t, disk.Partitions)
		// unmount even if createFilesForPartitions failed
		errUnmount := unmountPartitions(t, disk.Partitions)
		if err != nil {
			if errUnmount != nil {
				t.Errorf("couldn't unmount partitions: %v", errUnmount)
			}
			return err
		} else if errUnmount != nil {
			return errUnmount
		}

		// Mount device name substitution
		for _, d := range test.MntDevices {
			device := pickPartition(t, disk.Device, disk.Partitions, d.Label)
			// The device may not be on this disk, if it's not found here let's
			// assume we'll find it on another one and keep going
			if device != "" {
				test.Config = strings.Replace(test.Config, d.Substitution, device, -1)
			}
		}

		// Replace any instance of $disk<num> with the actual loop device
		// that got assigned to it
		test.Config = strings.Replace(test.Config, fmt.Sprintf("$disk%d", i), disk.Device, -1)

		if rootLocation == "" {
			rootLocation = getRootLocation(disk.Partitions)
		}
	}

	// Validation and cleanup deferral
	for i, disk := range test.Out {
		// Update out structure with mount points & devices
		setExpectedPartitionsDrive(test.In[i].Partitions, disk.Partitions)
	}

	if rootLocation == "" {
		return fmt.Errorf("ROOT filesystem not found! A partition labeled ROOT is requred")
	}

	// Let's make sure that all of the devices we needed to substitute names in
	// for were found
	for _, d := range test.MntDevices {
		if strings.Contains(test.Config, d.Substitution) {
			return fmt.Errorf("Didn't find a drive with label: %s", d.Substitution)
		}
	}

	// If we're not expecting the config to be bad, make sure it passes
	// validation.
	if !test.ConfigShouldBeBad {
		_, rpt, err := config.Parse([]byte(test.Config))
		if rpt.IsFatal() {
			return fmt.Errorf("test has bad config: %s", rpt.String())
		}
		if err != nil {
			return fmt.Errorf("error parsing config: %v", err)
		}
	}

	// Ignition config
	if err := ioutil.WriteFile(filepath.Join(tmpDirectory, "config.ign"), []byte(test.Config), 0666); err != nil {
		return fmt.Errorf("error writing config: %v", err)
	}

	// Ignition
	appendEnv := []string{
		"IGNITION_OEM_DEVICE=" + test.In[0].Partitions.GetPartition("OEM").Device,
		"IGNITION_OEM_LOOKASIDE_DIR=" + oemLookasideDir,
		"IGNITION_SYSTEM_CONFIG_DIR=" + systemConfigDir,
	}
	disksErr := runIgnition(t, ctx, "disks", rootLocation, tmpDirectory, appendEnv)
	if !negativeTests && disksErr != nil {
		return disksErr
	}
	filesErr := runIgnition(t, ctx, "files", rootLocation, tmpDirectory, appendEnv)
	if !negativeTests && filesErr != nil {
		return filesErr
	}
	if negativeTests && disksErr == nil && filesErr == nil {
		return fmt.Errorf("Expected failure and ignition succeeded")
	}

	for _, disk := range test.Out {
		if !negativeTests {
			err = validateDisk(t, disk)
			if err != nil {
				return err
			}
			err = validateFilesystems(t, disk.Partitions)
			if err != nil {
				return err
			}
			validateFilesDirectoriesAndLinks(t, disk.Partitions)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package filefixture

import (
	"archive/tar"
	"bytes"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/spacemonkeygo/errors"
	"polydawn.net/repeatr/lib/fs"
)

type FixtureFile struct {
	Metadata fs.Metadata
	Body     []byte
}

type Fixture struct {
	Name  string
	Files []FixtureFile
}

type ComparisonOptions uint32

const (
	ComparePerms = ComparisonOptions(0001)
	CompareMtime = ComparisonOptions(0002)
	CompareAtime = ComparisonOptions(0004)
	CompareUid   = ComparisonOptions(0010)
	CompareGid   = ComparisonOptions(0020)
	CompareSize  = ComparisonOptions(0040)
	CompareBody  = ComparisonOptions(0100)

	CompareDefaults = ComparePerms | CompareMtime | CompareUid | CompareGid |
		CompareSize | CompareBody
	CompareAll = CompareDefaults | CompareAtime
)

type filesByPath []FixtureFile

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

func (f Fixture) defaults() Fixture {
	for i, ff := range f.Files {
		f.Files[i] = defaults(ff)
	}
	return f
}

func defaults(f FixtureFile) FixtureFile {
	if f.Metadata.Typeflag == '\x00' {
		if f.Body == nil {
			if f.Metadata.Linkname != "" {
				f.Metadata.Typeflag = tar.TypeSymlink
			} else {
				f.Metadata.Typeflag = tar.TypeDir
			}
		} else {
			f.Metadata.Typeflag = tar.TypeReg
		}
	}
	if f.Metadata.Mode == 0 {
		switch f.Metadata.Typeflag {
		case tar.TypeDir:
			f.Metadata.Mode = 0755
		default:
			f.Metadata.Mode = 0644
		}
	}
	if f.Metadata.Size == 0 {
		f.Metadata.Size = int64(len(f.Body))
	}
	if f.Metadata.Uid == 0 {
		f.Metadata.Uid = 10000
	}
	if f.Metadata.Gid == 0 {
		f.Metadata.Gid = 10000
	}
	if f.Metadata.ModTime.IsZero() {
		f.Metadata.ModTime = time.Unix(0, 0).UTC()
	}
	if f.Metadata.AccessTime.IsZero() {
		f.Metadata.AccessTime = time.Unix(0, 0).UTC()
	}
	return f
}

/*
	Create files described by the fixtures on the real filesystem path given.
*/
func (ffs Fixture) Create(basePath string) {
	basePath, err := filepath.Abs(basePath)
	if err != nil {
		panic(errors.IOError.Wrap(err))
	}
	for _, f := range ffs.Files {
		fs.PlaceFile(basePath, f.Metadata, bytes.NewBuffer(f.Body))
	}
	// re-do time enforcement... in reverse order, so we cover our own tracks
	for i := len(ffs.Files) - 1; i >= 0; i-- {
		f := ffs.Files[i]
		if f.Metadata.Typeflag == tar.TypeDir {
			fs.PlaceDirTime(basePath, f.Metadata)
		}
	}
}

/*
	Scan a real filesystem and see it as fixture file descriptions.
	Usually used as a prelude to a pair of `Describe` calls followed by
	an equality assertion.

	Note that this loads all file bodies into memory at once, so it
	is not wise to use on large filesystems.

	Result will be sorted by filename, as per usual.
*/
func Scan(basePath string) Fixture {
	ffs := Fixture{fmt.Sprintf("Scan of %q", basePath), nil}
	preVisit := func(filenode *fs.FilewalkNode) error {
		if filenode.Err != nil {
			return filenode.Err
		}
		hdr, file := fs.ScanFile(basePath, filenode.Path)
		var body []byte
		if file != nil {
			defer file.Close()
			var err error
			body, err = ioutil.ReadAll(file)
			if err != nil {
				return err
			}
		}
		ffs.Files = append(ffs.Files, FixtureFile{hdr, body})
		return nil
	}
	if err := fs.Walk(basePath, preVisit, nil); err != nil {
		panic(err)
	}
	sort.Sort(filesByPath(ffs.Files))
	return ffs
}

/*
	Produces a string where every line describes a one entry in the
	set of file descriptions.  This is useful for handing into "ShouldResemble"
	assertions, which will not only pass/fail but do character diffs which
	effectively cover the whole structure in one shot.

*/
func (ffs Fixture) Describe(opts ComparisonOptions) string {
	lines := make([]string, len(ffs.Files))
	for i, f := range ffs.Files {
		lines[i] = f.Describe(opts)
	}
	return strings.Join(lines, "\n")
}

/*
	As per `FixtureFiles.Describe`, but this is for a single entry.
*/
func (ff FixtureFile) Describe(opts ComparisonOptions) string {
	parts := []struct {
		Key   string
		Value interface{}
	}{
		{"Name:%q", ff.Metadata.Name},
		{"Type:%q", ff.Metadata.Typeflag},
		{"Perms:%q", (map[bool]interface{}{true: "-", false: ff.Metadata.FileMode()})[opts&ComparePerms == 0]},
		{"Mtime:%q", (map[bool]interface{}{true: "-", false: ff.Metadata.ModTime.UTC()})[opts&CompareMtime == 0]},
		{"Atime:%q", (map[bool]interface{}{true: "-", false: ff.Metadata.AccessTime.UTC()})[opts&CompareAtime == 0]},
		{"Uid:%d", (map[bool]interface{}{true: "-", false: ff.Metadata.Uid})[opts&CompareUid == 0]},
		{"Gid:%d", (map[bool]interface{}{true: "-", false: ff.Metadata.Gid})[opts&CompareGid == 0]},
		{"DM:%d", ff.Metadata.Devmajor},
		{"Dm:%d", ff.Metadata.Devminor},
		{"Link:%q", ff.Metadata.Linkname},
		{"Size:%d", (map[bool]interface{}{true: "-", false: ff.Metadata.Size})[opts&CompareSize == 0]},
		{"Body:%q", (map[bool]interface{}{true: "-", false: ff.Body})[opts&CompareBody == 0]},
	}
	var pattern string
	var values []interface{}
	for _, part := range parts {
		pattern += "\t" + part.Key
		values = append(values, part.Value)
	}
	return fmt.Sprintf(pattern, values...)
}
<commit_msg>Use standard epoch value.<commit_after>package filefixture

import (
	"archive/tar"
	"bytes"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"sort"
	"strings"

	"github.com/spacemonkeygo/errors"
	"polydawn.net/repeatr/def"
	"polydawn.net/repeatr/lib/fs"
)

type FixtureFile struct {
	Metadata fs.Metadata
	Body     []byte
}

type Fixture struct {
	Name  string
	Files []FixtureFile
}

type ComparisonOptions uint32

const (
	ComparePerms = ComparisonOptions(0001)
	CompareMtime = ComparisonOptions(0002)
	CompareAtime = ComparisonOptions(0004)
	CompareUid   = ComparisonOptions(0010)
	CompareGid   = ComparisonOptions(0020)
	CompareSize  = ComparisonOptions(0040)
	CompareBody  = ComparisonOptions(0100)

	CompareDefaults = ComparePerms | CompareMtime | CompareUid | CompareGid |
		CompareSize | CompareBody
	CompareAll = CompareDefaults | CompareAtime
)

type filesByPath []FixtureFile

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

func (f Fixture) defaults() Fixture {
	for i, ff := range f.Files {
		f.Files[i] = defaults(ff)
	}
	return f
}

func defaults(f FixtureFile) FixtureFile {
	if f.Metadata.Typeflag == '\x00' {
		if f.Body == nil {
			if f.Metadata.Linkname != "" {
				f.Metadata.Typeflag = tar.TypeSymlink
			} else {
				f.Metadata.Typeflag = tar.TypeDir
			}
		} else {
			f.Metadata.Typeflag = tar.TypeReg
		}
	}
	if f.Metadata.Mode == 0 {
		switch f.Metadata.Typeflag {
		case tar.TypeDir:
			f.Metadata.Mode = 0755
		default:
			f.Metadata.Mode = 0644
		}
	}
	if f.Metadata.Size == 0 {
		f.Metadata.Size = int64(len(f.Body))
	}
	if f.Metadata.Uid == 0 {
		f.Metadata.Uid = 10000
	}
	if f.Metadata.Gid == 0 {
		f.Metadata.Gid = 10000
	}
	if f.Metadata.ModTime.IsZero() {
		f.Metadata.ModTime = def.Epochwhen
	}
	if f.Metadata.AccessTime.IsZero() {
		f.Metadata.AccessTime = def.Epochwhen
	}
	return f
}

/*
	Create files described by the fixtures on the real filesystem path given.
*/
func (ffs Fixture) Create(basePath string) {
	basePath, err := filepath.Abs(basePath)
	if err != nil {
		panic(errors.IOError.Wrap(err))
	}
	for _, f := range ffs.Files {
		fs.PlaceFile(basePath, f.Metadata, bytes.NewBuffer(f.Body))
	}
	// re-do time enforcement... in reverse order, so we cover our own tracks
	for i := len(ffs.Files) - 1; i >= 0; i-- {
		f := ffs.Files[i]
		if f.Metadata.Typeflag == tar.TypeDir {
			fs.PlaceDirTime(basePath, f.Metadata)
		}
	}
}

/*
	Scan a real filesystem and see it as fixture file descriptions.
	Usually used as a prelude to a pair of `Describe` calls followed by
	an equality assertion.

	Note that this loads all file bodies into memory at once, so it
	is not wise to use on large filesystems.

	Result will be sorted by filename, as per usual.
*/
func Scan(basePath string) Fixture {
	ffs := Fixture{fmt.Sprintf("Scan of %q", basePath), nil}
	preVisit := func(filenode *fs.FilewalkNode) error {
		if filenode.Err != nil {
			return filenode.Err
		}
		hdr, file := fs.ScanFile(basePath, filenode.Path)
		var body []byte
		if file != nil {
			defer file.Close()
			var err error
			body, err = ioutil.ReadAll(file)
			if err != nil {
				return err
			}
		}
		ffs.Files = append(ffs.Files, FixtureFile{hdr, body})
		return nil
	}
	if err := fs.Walk(basePath, preVisit, nil); err != nil {
		panic(err)
	}
	sort.Sort(filesByPath(ffs.Files))
	return ffs
}

/*
	Produces a string where every line describes a one entry in the
	set of file descriptions.  This is useful for handing into "ShouldResemble"
	assertions, which will not only pass/fail but do character diffs which
	effectively cover the whole structure in one shot.

*/
func (ffs Fixture) Describe(opts ComparisonOptions) string {
	lines := make([]string, len(ffs.Files))
	for i, f := range ffs.Files {
		lines[i] = f.Describe(opts)
	}
	return strings.Join(lines, "\n")
}

/*
	As per `FixtureFiles.Describe`, but this is for a single entry.
*/
func (ff FixtureFile) Describe(opts ComparisonOptions) string {
	parts := []struct {
		Key   string
		Value interface{}
	}{
		{"Name:%q", ff.Metadata.Name},
		{"Type:%q", ff.Metadata.Typeflag},
		{"Perms:%q", (map[bool]interface{}{true: "-", false: ff.Metadata.FileMode()})[opts&ComparePerms == 0]},
		{"Mtime:%q", (map[bool]interface{}{true: "-", false: ff.Metadata.ModTime.UTC()})[opts&CompareMtime == 0]},
		{"Atime:%q", (map[bool]interface{}{true: "-", false: ff.Metadata.AccessTime.UTC()})[opts&CompareAtime == 0]},
		{"Uid:%d", (map[bool]interface{}{true: "-", false: ff.Metadata.Uid})[opts&CompareUid == 0]},
		{"Gid:%d", (map[bool]interface{}{true: "-", false: ff.Metadata.Gid})[opts&CompareGid == 0]},
		{"DM:%d", ff.Metadata.Devmajor},
		{"Dm:%d", ff.Metadata.Devminor},
		{"Link:%q", ff.Metadata.Linkname},
		{"Size:%d", (map[bool]interface{}{true: "-", false: ff.Metadata.Size})[opts&CompareSize == 0]},
		{"Body:%q", (map[bool]interface{}{true: "-", false: ff.Body})[opts&CompareBody == 0]},
	}
	var pattern string
	var values []interface{}
	for _, part := range parts {
		pattern += "\t" + part.Key
		values = append(values, part.Value)
	}
	return fmt.Sprintf(pattern, values...)
}
<|endoftext|>


<commit_before>package php

import (
	"stephensearles.com/php/ast"
	"stephensearles.com/php/token"
)

func (p *Parser) parseStmt() ast.Statement {
	switch p.current.typ {
	case token.BlockBegin:
		p.backup()
		return p.parseBlock()
	case token.Global:
		p.next()
		g := &ast.GlobalDeclaration{
			Identifiers: make([]*ast.Variable, 0, 1),
		}
		for p.current.typ == token.VariableOperator {
			variable, ok := p.parseVariable().(*ast.Variable)
			if !ok {
				p.errorf("global declarations must be of standard variables")
				break
			}
			g.Identifiers = append(g.Identifiers, variable)
			if p.peek().typ != token.Comma {
				break
			}
			p.expect(token.Comma)
			p.next()
		}
		p.expectStmtEnd()
		return g
	case token.Namespace:
		p.expect(token.Identifier)
		p.expectStmtEnd()
		// We are ignoring this for now
		return nil
	case token.Use:
		p.expect(token.Identifier)
		if p.peek().typ == token.AsOperator {
			p.expect(token.AsOperator)
			p.expect(token.Identifier)
		}
		p.expectStmtEnd()
		// We are ignoring this for now
		return nil
	case token.Static:
		s := &ast.StaticVariableDeclaration{Declarations: make([]ast.Expression, 0)}
		for {
			p.expect(token.VariableOperator)
			p.expect(token.Identifier)
			v := ast.NewVariable(p.current.val)
			if p.peek().typ == token.AssignmentOperator {
				p.expect(token.AssignmentOperator)
				op := p.current.val
				p.expect(token.Null, token.StringLiteral, token.BooleanLiteral, token.NumberLiteral, token.Array)
				switch p.current.typ {
				case token.Array:
					s.Declarations = append(s.Declarations, &ast.AssignmentExpression{Assignee: v, Value: p.parseArrayDeclaration(), Operator: op})
				default:
					s.Declarations = append(s.Declarations, &ast.AssignmentExpression{Assignee: v, Value: p.parseLiteral(), Operator: op})
				}
			}
			s.Declarations = append(s.Declarations, v)
			if p.peek().typ != token.Comma {
				break
			}
			p.next()
		}
		return s
	case token.VariableOperator, token.UnaryOperator:
		expr := ast.ExpressionStmt{p.parseExpression()}
		p.expectStmtEnd()
		return expr
	case token.Print:
		requireParen := false
		if p.peek().typ == token.OpenParen {
			p.expect(token.OpenParen)
			requireParen = true
		}
		stmt := ast.Echo(p.parseNextExpression())
		if requireParen {
			p.expect(token.CloseParen)
		}
		p.expectStmtEnd()
		return stmt
	case token.Function:
		return p.parseFunctionStmt()
	case token.PHPEnd:
		if p.peek().typ == token.EOF {
			return nil
		}
		p.expect(token.HTML)
		expr := ast.Echo(&ast.Literal{Type: ast.String, Value: p.current.val})
		p.next()
		if p.current.typ != token.EOF {
			p.expectCurrent(token.PHPBegin)
		}
		return expr
	case token.Echo:
		exprs := []ast.Expression{
			p.parseNextExpression(),
		}
		for p.peek().typ == token.Comma {
			p.expect(token.Comma)
			exprs = append(exprs, p.parseNextExpression())
		}
		p.expectStmtEnd()
		return ast.Echo(exprs...)
	case token.If:
		return p.parseIf()
	case token.While:
		return p.parseWhile()
	case token.Do:
		return p.parseDo()
	case token.For:
		return p.parseFor()
	case token.Foreach:
		return p.parseForeach()
	case token.Switch:
		return p.parseSwitch()
	case token.Abstract, token.Final, token.Class:
		return p.parseClass()
	case token.Interface:
		return p.parseInterface()
	case token.Return:
		p.next()
		stmt := ast.ReturnStmt{}
		if p.current.typ != token.StatementEnd {
			stmt.Expression = p.parseExpression()
			p.expectStmtEnd()
		}
		return stmt
	case token.Break:
		p.next()
		stmt := ast.BreakStmt{}
		if p.current.typ != token.StatementEnd {
			stmt.Expression = p.parseExpression()
			p.expectStmtEnd()
		}
		return stmt
	case token.Continue:
		p.next()
		stmt := ast.ContinueStmt{}
		if p.current.typ != token.StatementEnd {
			stmt.Expression = p.parseExpression()
			p.expectStmtEnd()
		}
		return stmt
	case token.Throw:
		stmt := ast.ThrowStmt{Expression: p.parseNextExpression()}
		p.expectStmtEnd()
		return stmt
	case token.Exit:
		stmt := ast.ExitStmt{}
		if p.peek().typ == token.OpenParen {
			p.expect(token.OpenParen)
			if p.peek().typ != token.CloseParen {
				stmt.Expression = p.parseNextExpression()
			}
			p.expect(token.CloseParen)
		}
		p.expectStmtEnd()
		return stmt
	case token.Try:
		stmt := &ast.TryStmt{}
		stmt.TryBlock = p.parseBlock()
		for p.expect(token.Catch); p.current.typ == token.Catch; p.next() {
			caught := &ast.CatchStmt{}
			p.expect(token.OpenParen)
			p.expect(token.Identifier)
			caught.CatchType = p.current.val
			p.expect(token.VariableOperator)
			p.expect(token.Identifier)
			caught.CatchVar = ast.NewVariable(p.current.val)
			p.expect(token.CloseParen)
			caught.CatchBlock = p.parseBlock()
			stmt.CatchStmts = append(stmt.CatchStmts, caught)
		}
		p.backup()
		return stmt
	case token.IgnoreErrorOperator:
		// Ignore this operator
		p.next()
		return p.parseStmt()
	case token.StatementEnd:
		// this is an empty statement
		return &ast.EmptyStatement{}
	default:
		expr := p.parseExpression()
		if expr != nil {
			p.expectStmtEnd()
			return ast.ExpressionStmt{expr}
		}
		p.errorf("Found %s, statement or expression", p.current)
		return nil
	}
}

func (p *Parser) expectStmtEnd() {
	if p.peek().typ != token.PHPEnd {
		p.expect(token.StatementEnd)
	}
}
<commit_msg>Fixed a mistake with not expecting a statement end<commit_after>package php

import (
	"stephensearles.com/php/ast"
	"stephensearles.com/php/token"
)

func (p *Parser) parseStmt() ast.Statement {
	switch p.current.typ {
	case token.BlockBegin:
		p.backup()
		return p.parseBlock()
	case token.Global:
		p.next()
		g := &ast.GlobalDeclaration{
			Identifiers: make([]*ast.Variable, 0, 1),
		}
		for p.current.typ == token.VariableOperator {
			variable, ok := p.parseVariable().(*ast.Variable)
			if !ok {
				p.errorf("global declarations must be of standard variables")
				break
			}
			g.Identifiers = append(g.Identifiers, variable)
			if p.peek().typ != token.Comma {
				break
			}
			p.expect(token.Comma)
			p.next()
		}
		p.expectStmtEnd()
		return g
	case token.Namespace:
		p.expect(token.Identifier)
		p.expectStmtEnd()
		// We are ignoring this for now
		return nil
	case token.Use:
		p.expect(token.Identifier)
		if p.peek().typ == token.AsOperator {
			p.expect(token.AsOperator)
			p.expect(token.Identifier)
		}
		p.expectStmtEnd()
		// We are ignoring this for now
		return nil
	case token.Static:
		s := &ast.StaticVariableDeclaration{Declarations: make([]ast.Expression, 0)}
		for {
			p.expect(token.VariableOperator)
			p.expect(token.Identifier)
			v := ast.NewVariable(p.current.val)
			if p.peek().typ == token.AssignmentOperator {
				p.expect(token.AssignmentOperator)
				op := p.current.val
				p.expect(token.Null, token.StringLiteral, token.BooleanLiteral, token.NumberLiteral, token.Array)
				switch p.current.typ {
				case token.Array:
					s.Declarations = append(s.Declarations, &ast.AssignmentExpression{Assignee: v, Value: p.parseArrayDeclaration(), Operator: op})
				default:
					s.Declarations = append(s.Declarations, &ast.AssignmentExpression{Assignee: v, Value: p.parseLiteral(), Operator: op})
				}
			}
			s.Declarations = append(s.Declarations, v)
			if p.peek().typ != token.Comma {
				break
			}
			p.next()
		}
		p.expectStmtEnd()
		return s
	case token.VariableOperator, token.UnaryOperator:
		expr := ast.ExpressionStmt{p.parseExpression()}
		p.expectStmtEnd()
		return expr
	case token.Print:
		requireParen := false
		if p.peek().typ == token.OpenParen {
			p.expect(token.OpenParen)
			requireParen = true
		}
		stmt := ast.Echo(p.parseNextExpression())
		if requireParen {
			p.expect(token.CloseParen)
		}
		p.expectStmtEnd()
		return stmt
	case token.Function:
		return p.parseFunctionStmt()
	case token.PHPEnd:
		if p.peek().typ == token.EOF {
			return nil
		}
		p.expect(token.HTML)
		expr := ast.Echo(&ast.Literal{Type: ast.String, Value: p.current.val})
		p.next()
		if p.current.typ != token.EOF {
			p.expectCurrent(token.PHPBegin)
		}
		return expr
	case token.Echo:
		exprs := []ast.Expression{
			p.parseNextExpression(),
		}
		for p.peek().typ == token.Comma {
			p.expect(token.Comma)
			exprs = append(exprs, p.parseNextExpression())
		}
		p.expectStmtEnd()
		return ast.Echo(exprs...)
	case token.If:
		return p.parseIf()
	case token.While:
		return p.parseWhile()
	case token.Do:
		return p.parseDo()
	case token.For:
		return p.parseFor()
	case token.Foreach:
		return p.parseForeach()
	case token.Switch:
		return p.parseSwitch()
	case token.Abstract, token.Final, token.Class:
		return p.parseClass()
	case token.Interface:
		return p.parseInterface()
	case token.Return:
		p.next()
		stmt := ast.ReturnStmt{}
		if p.current.typ != token.StatementEnd {
			stmt.Expression = p.parseExpression()
			p.expectStmtEnd()
		}
		return stmt
	case token.Break:
		p.next()
		stmt := ast.BreakStmt{}
		if p.current.typ != token.StatementEnd {
			stmt.Expression = p.parseExpression()
			p.expectStmtEnd()
		}
		return stmt
	case token.Continue:
		p.next()
		stmt := ast.ContinueStmt{}
		if p.current.typ != token.StatementEnd {
			stmt.Expression = p.parseExpression()
			p.expectStmtEnd()
		}
		return stmt
	case token.Throw:
		stmt := ast.ThrowStmt{Expression: p.parseNextExpression()}
		p.expectStmtEnd()
		return stmt
	case token.Exit:
		stmt := ast.ExitStmt{}
		if p.peek().typ == token.OpenParen {
			p.expect(token.OpenParen)
			if p.peek().typ != token.CloseParen {
				stmt.Expression = p.parseNextExpression()
			}
			p.expect(token.CloseParen)
		}
		p.expectStmtEnd()
		return stmt
	case token.Try:
		stmt := &ast.TryStmt{}
		stmt.TryBlock = p.parseBlock()
		for p.expect(token.Catch); p.current.typ == token.Catch; p.next() {
			caught := &ast.CatchStmt{}
			p.expect(token.OpenParen)
			p.expect(token.Identifier)
			caught.CatchType = p.current.val
			p.expect(token.VariableOperator)
			p.expect(token.Identifier)
			caught.CatchVar = ast.NewVariable(p.current.val)
			p.expect(token.CloseParen)
			caught.CatchBlock = p.parseBlock()
			stmt.CatchStmts = append(stmt.CatchStmts, caught)
		}
		p.backup()
		return stmt
	case token.IgnoreErrorOperator:
		// Ignore this operator
		p.next()
		return p.parseStmt()
	case token.StatementEnd:
		// this is an empty statement
		return &ast.EmptyStatement{}
	default:
		expr := p.parseExpression()
		if expr != nil {
			p.expectStmtEnd()
			return ast.ExpressionStmt{expr}
		}
		p.errorf("Found %s, statement or expression", p.current)
		return nil
	}
}

func (p *Parser) expectStmtEnd() {
	if p.peek().typ != token.PHPEnd {
		p.expect(token.StatementEnd)
	}
}
<|endoftext|>


<commit_before>// +build json

package transport

import (
	"encoding/json"

	"github.com/v2ray/v2ray-core/transport/hub/kcpv"
)

func (this *Config) UnmarshalJSON(data []byte) error {
	type JsonConfig struct {
		ConnectionReuse bool         `json:"connectionReuse"`
		EnableKcp       bool         `json:"EnableKCP,omitempty"`
		KcpConfig       *kcpv.Config `json:"KcpConfig,omitempty"`
	}
	jsonConfig := &JsonConfig{
		ConnectionReuse: true,
		EnableKcp:       false,
	}
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return err
	}
	this.ConnectionReuse = jsonConfig.ConnectionReuse
	this.enableKcp = jsonConfig.EnableKcp
	this.kcpConfig = jsonConfig.KcpConfig
	if jsonConfig.KcpConfig.AdvancedConfigs == nil {
		jsonConfig.KcpConfig.AdvancedConfigs = kcpv.DefaultAdvancedConfigs
	}

	return nil
}
<commit_msg>Fix: check jsonConfig.KcpConfig<commit_after>// +build json

package transport

import (
	"encoding/json"

	"github.com/v2ray/v2ray-core/common/log"
	"github.com/v2ray/v2ray-core/transport/hub/kcpv"
)

func (this *Config) UnmarshalJSON(data []byte) error {
	type JsonConfig struct {
		ConnectionReuse bool         `json:"connectionReuse"`
		EnableKcp       bool         `json:"EnableKCP,omitempty"`
		KcpConfig       *kcpv.Config `json:"KcpConfig,omitempty"`
	}
	jsonConfig := &JsonConfig{
		ConnectionReuse: true,
		EnableKcp:       false,
	}
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return err
	}
	this.ConnectionReuse = jsonConfig.ConnectionReuse
	this.enableKcp = jsonConfig.EnableKcp
	if jsonConfig.KcpConfig != nil {
		this.kcpConfig = jsonConfig.KcpConfig
		if jsonConfig.KcpConfig.AdvancedConfigs == nil {
			jsonConfig.KcpConfig.AdvancedConfigs = kcpv.DefaultAdvancedConfigs
		}
	} else {
		if jsonConfig.EnableKcp {
			log.Error("transport: You have enabled KCP but no configure is given")
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package http

import (
	"fmt"
	"net/http"
	"net/url"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"

	"github.com/go-kit/kit/endpoint"
)

// Client wraps a URL and provides a method that implements endpoint.Endpoint.
type Client struct {
	client         *http.Client
	method         string
	tgt            *url.URL
	enc            EncodeRequestFunc
	dec            DecodeResponseFunc
	before         []RequestFunc
	bufferedStream bool
}

// NewClient returns a
func NewClient(method string, tgt *url.URL, enc EncodeRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client {
	c := &Client{
		client:         http.DefaultClient,
		method:         method,
		tgt:            tgt,
		enc:            enc,
		dec:            dec,
		before:         []RequestFunc{},
		bufferedStream: false,
	}
	for _, option := range options {
		option(c)
	}
	return c
}

// ClientOption sets an optional parameter for clients.
type ClientOption func(*Client)

// SetClient sets the underlying HTTP client used for requests.
// By default, http.DefaultClient is used.
func SetClient(client *http.Client) ClientOption {
	return func(c *Client) { c.client = client }
}

// SetClientBefore sets the RequestFuncs that are applied to the outgoing HTTP
// request before it's invoked.
func SetClientBefore(before ...RequestFunc) ClientOption {
	return func(c *Client) { c.before = before }
}

// SetBufferedStream sets whether the Response.Body is left open, allowing it
// to be read from later. Useful for transporting a file as a buffered stream.
func SetBufferedStream(buffered bool) ClientOption {
	return func(c *Client) { c.bufferedStream = buffered }
}

// Endpoint returns a usable endpoint that will invoke the RPC specified by
// the client.
func (c Client) Endpoint() endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		ctx, cancel := context.WithCancel(ctx)
		defer cancel()

		req, err := http.NewRequest(c.method, c.tgt.String(), nil)
		if err != nil {
			return nil, fmt.Errorf("NewRequest: %v", err)
		}

		if err = c.enc(req, request); err != nil {
			return nil, fmt.Errorf("Encode: %v", err)
		}

		for _, f := range c.before {
			ctx = f(ctx, req)
		}

		resp, err := ctxhttp.Do(ctx, c.client, req)
		if err != nil {
			return nil, fmt.Errorf("Do: %v", err)
		}
		if !c.bufferedStream {
			defer resp.Body.Close()
		}

		response, err := c.dec(resp)
		if err != nil {
			return nil, fmt.Errorf("Decode: %v", err)
		}

		return response, nil
	}
}
<commit_msg>transport/http: fix comment on NewClient<commit_after>package http

import (
	"fmt"
	"net/http"
	"net/url"

	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"

	"github.com/go-kit/kit/endpoint"
)

// Client wraps a URL and provides a method that implements endpoint.Endpoint.
type Client struct {
	client         *http.Client
	method         string
	tgt            *url.URL
	enc            EncodeRequestFunc
	dec            DecodeResponseFunc
	before         []RequestFunc
	bufferedStream bool
}

// NewClient constructs a usable Client for a single remote endpoint.
func NewClient(
	method string,
	tgt *url.URL,
	enc EncodeRequestFunc,
	dec DecodeResponseFunc,
	options ...ClientOption,
) *Client {
	c := &Client{
		client:         http.DefaultClient,
		method:         method,
		tgt:            tgt,
		enc:            enc,
		dec:            dec,
		before:         []RequestFunc{},
		bufferedStream: false,
	}
	for _, option := range options {
		option(c)
	}
	return c
}

// ClientOption sets an optional parameter for clients.
type ClientOption func(*Client)

// SetClient sets the underlying HTTP client used for requests.
// By default, http.DefaultClient is used.
func SetClient(client *http.Client) ClientOption {
	return func(c *Client) { c.client = client }
}

// SetClientBefore sets the RequestFuncs that are applied to the outgoing HTTP
// request before it's invoked.
func SetClientBefore(before ...RequestFunc) ClientOption {
	return func(c *Client) { c.before = before }
}

// SetBufferedStream sets whether the Response.Body is left open, allowing it
// to be read from later. Useful for transporting a file as a buffered stream.
func SetBufferedStream(buffered bool) ClientOption {
	return func(c *Client) { c.bufferedStream = buffered }
}

// Endpoint returns a usable endpoint that will invoke the RPC specified by
// the client.
func (c Client) Endpoint() endpoint.Endpoint {
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		ctx, cancel := context.WithCancel(ctx)
		defer cancel()

		req, err := http.NewRequest(c.method, c.tgt.String(), nil)
		if err != nil {
			return nil, fmt.Errorf("NewRequest: %v", err)
		}

		if err = c.enc(req, request); err != nil {
			return nil, fmt.Errorf("Encode: %v", err)
		}

		for _, f := range c.before {
			ctx = f(ctx, req)
		}

		resp, err := ctxhttp.Do(ctx, c.client, req)
		if err != nil {
			return nil, fmt.Errorf("Do: %v", err)
		}
		if !c.bufferedStream {
			defer resp.Body.Close()
		}

		response, err := c.dec(resp)
		if err != nil {
			return nil, fmt.Errorf("Decode: %v", err)
		}

		return response, nil
	}
}
<|endoftext|>


<commit_before>package main

import (
	"testing"
	. "github.com/onsi/gomega"
)

func Test_GetConfigWillReturnTheDefaultValues(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	result := GetConfig("", "", "")

	Expect(result.HoverflyHost).To(Equal("localhost"))
	Expect(result.HoverflyAdminPort).To(Equal("8888"))
	Expect(result.HoverflyProxyPort).To(Equal("8500"))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}

func Test_GetConfigOverridesDefaultValueWithAHoverflyHost(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	hoverflyHost := "testhost"
	result := GetConfig(hoverflyHost, "", "")

	Expect(result.HoverflyHost).To(Equal(hoverflyHost))
	Expect(result.HoverflyAdminPort).To(Equal("8888"))
	Expect(result.HoverflyProxyPort).To(Equal("8500"))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}

func Test_GetConfigOverridesDefaultValueWithAHoverflyAdminPort(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	hoverflyAdminPort := "5"
	result := GetConfig("", hoverflyAdminPort, "")

	Expect(result.HoverflyHost).To(Equal("localhost"))
	Expect(result.HoverflyAdminPort).To(Equal(hoverflyAdminPort))
	Expect(result.HoverflyProxyPort).To(Equal("8500"))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}

func Test_GetConfigOverridesDefaultValueWithAHoverflyProxyPort(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	hoverflyProxyPort := "7"
	result := GetConfig("", "", hoverflyProxyPort)

	Expect(result.HoverflyHost).To(Equal("localhost"))
	Expect(result.HoverflyAdminPort).To(Equal("8888"))
	Expect(result.HoverflyProxyPort).To(Equal(hoverflyProxyPort))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}

func Test_GetConfigOverridesDefaultValueWithAllOverrides(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	hoverflyHost := "specto.io"
	hoverflyAdminPort := "7654"
	hoverflyProxyPort := "1523"
	result := GetConfig(hoverflyHost, hoverflyAdminPort, hoverflyProxyPort)

	Expect(result.HoverflyHost).To(Equal(hoverflyHost))
	Expect(result.HoverflyAdminPort).To(Equal(hoverflyAdminPort))
	Expect(result.HoverflyProxyPort).To(Equal(hoverflyProxyPort))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}<commit_msg>Testing the writing of config to disk<commit_after>package main

import (
	"testing"
	. "github.com/onsi/gomega"
	"os"
	"io/ioutil"
	"gopkg.in/yaml.v2"
)

func Test_GetConfigWillReturnTheDefaultValues(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	result := GetConfig("", "", "")

	Expect(result.HoverflyHost).To(Equal("localhost"))
	Expect(result.HoverflyAdminPort).To(Equal("8888"))
	Expect(result.HoverflyProxyPort).To(Equal("8500"))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}

func Test_GetConfigOverridesDefaultValueWithAHoverflyHost(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	hoverflyHost := "testhost"
	result := GetConfig(hoverflyHost, "", "")

	Expect(result.HoverflyHost).To(Equal(hoverflyHost))
	Expect(result.HoverflyAdminPort).To(Equal("8888"))
	Expect(result.HoverflyProxyPort).To(Equal("8500"))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}

func Test_GetConfigOverridesDefaultValueWithAHoverflyAdminPort(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	hoverflyAdminPort := "5"
	result := GetConfig("", hoverflyAdminPort, "")

	Expect(result.HoverflyHost).To(Equal("localhost"))
	Expect(result.HoverflyAdminPort).To(Equal(hoverflyAdminPort))
	Expect(result.HoverflyProxyPort).To(Equal("8500"))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}

func Test_GetConfigOverridesDefaultValueWithAHoverflyProxyPort(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	hoverflyProxyPort := "7"
	result := GetConfig("", "", hoverflyProxyPort)

	Expect(result.HoverflyHost).To(Equal("localhost"))
	Expect(result.HoverflyAdminPort).To(Equal("8888"))
	Expect(result.HoverflyProxyPort).To(Equal(hoverflyProxyPort))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}

func Test_GetConfigOverridesDefaultValueWithAllOverrides(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	hoverflyHost := "specto.io"
	hoverflyAdminPort := "7654"
	hoverflyProxyPort := "1523"
	result := GetConfig(hoverflyHost, hoverflyAdminPort, hoverflyProxyPort)

	Expect(result.HoverflyHost).To(Equal(hoverflyHost))
	Expect(result.HoverflyAdminPort).To(Equal(hoverflyAdminPort))
	Expect(result.HoverflyProxyPort).To(Equal(hoverflyProxyPort))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}

func Test_ConfigWriteToFileWritesTheConfigObjectToAFileInAYamlFormat(t *testing.T) {
	RegisterTestingT(t)

	SetConfigurationDefaults()
	config := GetConfig("", "", "")

	workingDir, _ := os.Getwd()
	config.WriteToFile(workingDir)

	data, _ := ioutil.ReadFile(workingDir + "/config.yaml")
	os.Remove(workingDir + "/config.yaml")

	var result Config
	yaml.Unmarshal(data, &result)

	Expect(result.HoverflyHost).To(Equal("localhost"))
	Expect(result.HoverflyAdminPort).To(Equal("8888"))
	Expect(result.HoverflyProxyPort).To(Equal("8500"))
	Expect(result.SpectoLabHost).To(Equal("localhost"))
	Expect(result.SpectoLabPort).To(Equal("81"))
	Expect(result.SpectoLabApiKey).To(Equal(""))
}<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"gopkg.in/h2non/bimg.v1"
	"gopkg.in/h2non/filetype.v0"
)

func indexController(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		ErrorReply(w, ErrNotFound)
		return
	}

	body, _ := json.Marshal(CurrentVersions)
	w.Header().Set("Content-Type", "application/json")
	w.Write(body)
}

func healthController(w http.ResponseWriter, r *http.Request) {
	health := GetHealthStats()
	body, _ := json.Marshal(health)
	w.Header().Set("Content-Type", "application/json")
	w.Write(body)
}

func imageController(o ServerOptions, operation Operation) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		var imageSource = MatchSource(req)
		if imageSource == nil {
			ErrorReply(w, ErrMissingImageSource)
			return
		}

		buf, err := imageSource.GetImage(req)
		if err != nil {
			ErrorReply(w, NewError(err.Error(), BadRequest))
			return
		}

		if len(buf) == 0 {
			ErrorReply(w, ErrEmptyBody)
			return
		}

		imageHandler(w, req, buf, operation)
	}
}

func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, Operation Operation) {
	// Infer the body MIME type via mimesniff algorithm
	mimeType := http.DetectContentType(buf)

	// If cannot infer the type, infer it via magic numbers
	if mimeType == "application/octet-stream" {
		kind, err := filetype.Get(buf)
		if err != nil && kind.MIME.Value != "" {
			mimeType = kind.MIME.Value
		}
	}

	// Infer text/plain responses as potential SVG image
	if strings.Contains(mimeType, "text/plain") && len(buf) > 256 {
		if bimg.IsSVGImage(buf) {
			mimeType = "image/svg+xml"
		}
	}

	// Finally check if image MIME type is supported
	if IsImageMimeTypeSupported(mimeType) == false {
		ErrorReply(w, ErrUnsupportedMedia)
		return
	}

	opts := readParams(r.URL.Query())
	if opts.Type != "" && ImageType(opts.Type) == 0 {
		ErrorReply(w, ErrOutputFormat)
		return
	}

	image, err := Operation.Run(buf, opts)
	if err != nil {
		ErrorReply(w, NewError("Error while processing the image: "+err.Error(), BadRequest))
		return
	}

	w.Header().Set("Content-Type", image.Mime)
	w.Write(image.Body)
}

func formController(w http.ResponseWriter, r *http.Request) {
	operations := []struct {
		name   string
		method string
		args   string
	}{
		{"Resize", "resize", "width=300&height=200&type=jpeg"},
		{"Force resize", "resize", "width=300&height=200&force=true"},
		{"Crop", "crop", "width=300&quality=95"},
		{"Extract", "extract", "top=100&left=100&areawidth=300&areaheight=150"},
		{"Enlarge", "enlarge", "width=1440&height=900&quality=95"},
		{"Rotate", "rotate", "rotate=180"},
		{"Flip", "flip", ""},
		{"Flop", "flop", ""},
		{"Thumbnail", "thumbnail", "width=100"},
		{"Zoom", "zoom", "factor=2&areawidth=300&top=80&left=80"},
		{"Color space (black&white)", "resize", "width=400&height=300&colorspace=bw"},
		{"Add watermark", "watermark", "textwidth=100&text=Hello&font=sans%2012&opacity=0.5&color=255,200,50"},
		{"Convert format", "convert", "type=png"},
		{"Image metadata", "info", ""},
	}

	html := ""

	for _, form := range operations {
		html += fmt.Sprintf(`

%s

    <form method="POST" action="/%s?%s" enctype="multipart/form-data">
      <input type="file" name="file" />
      <input type="submit" value="Upload" />
    </form>`, form.name, form.method, form.args)
	}

	html += ""

	w.Header().Set("Content-Type", "text/html")
	w.Write([]byte(html))
}
<commit_msg>refactor(controllers)<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"gopkg.in/h2non/bimg.v1"
	"gopkg.in/h2non/filetype.v0"
)

func indexController(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		ErrorReply(w, ErrNotFound)
		return
	}

	body, _ := json.Marshal(CurrentVersions)
	w.Header().Set("Content-Type", "application/json")
	w.Write(body)
}

func healthController(w http.ResponseWriter, r *http.Request) {
	health := GetHealthStats()
	body, _ := json.Marshal(health)
	w.Header().Set("Content-Type", "application/json")
	w.Write(body)
}

func imageController(o ServerOptions, operation Operation) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		var imageSource = MatchSource(req)
		if imageSource == nil {
			ErrorReply(w, ErrMissingImageSource)
			return
		}

		buf, err := imageSource.GetImage(req)
		if err != nil {
			ErrorReply(w, NewError(err.Error(), BadRequest))
			return
		}

		if len(buf) == 0 {
			ErrorReply(w, ErrEmptyBody)
			return
		}

		imageHandler(w, req, buf, operation)
	}
}

func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, Operation Operation) {
	// Infer the body MIME type via mimesniff algorithm
	mimeType := http.DetectContentType(buf)

	// If cannot infer the type, infer it via magic numbers
	if mimeType == "application/octet-stream" {
		kind, err := filetype.Get(buf)
		if err != nil && kind.MIME.Value != "" {
			mimeType = kind.MIME.Value
		}
	}

	// Infer text/plain responses as potential SVG image
	if strings.Contains(mimeType, "text/plain") && len(buf) > 8 {
		if bimg.IsSVGImage(buf) {
			mimeType = "image/svg+xml"
		}
	}

	// Finally check if image MIME type is supported
	if IsImageMimeTypeSupported(mimeType) == false {
		ErrorReply(w, ErrUnsupportedMedia)
		return
	}

	opts := readParams(r.URL.Query())
	if opts.Type != "" && ImageType(opts.Type) == 0 {
		ErrorReply(w, ErrOutputFormat)
		return
	}

	image, err := Operation.Run(buf, opts)
	if err != nil {
		ErrorReply(w, NewError("Error while processing the image: "+err.Error(), BadRequest))
		return
	}

	w.Header().Set("Content-Type", image.Mime)
	w.Write(image.Body)
}

func formController(w http.ResponseWriter, r *http.Request) {
	operations := []struct {
		name   string
		method string
		args   string
	}{
		{"Resize", "resize", "width=300&height=200&type=jpeg"},
		{"Force resize", "resize", "width=300&height=200&force=true"},
		{"Crop", "crop", "width=300&quality=95"},
		{"Extract", "extract", "top=100&left=100&areawidth=300&areaheight=150"},
		{"Enlarge", "enlarge", "width=1440&height=900&quality=95"},
		{"Rotate", "rotate", "rotate=180"},
		{"Flip", "flip", ""},
		{"Flop", "flop", ""},
		{"Thumbnail", "thumbnail", "width=100"},
		{"Zoom", "zoom", "factor=2&areawidth=300&top=80&left=80"},
		{"Color space (black&white)", "resize", "width=400&height=300&colorspace=bw"},
		{"Add watermark", "watermark", "textwidth=100&text=Hello&font=sans%2012&opacity=0.5&color=255,200,50"},
		{"Convert format", "convert", "type=png"},
		{"Image metadata", "info", ""},
	}

	html := ""

	for _, form := range operations {
		html += fmt.Sprintf(`

%s

    <form method="POST" action="/%s?%s" enctype="multipart/form-data">
      <input type="file" name="file" />
      <input type="submit" value="Upload" />
    </form>`, form.name, form.method, form.args)
	}

	html += ""

	w.Header().Set("Content-Type", "text/html")
	w.Write([]byte(html))
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"mime"
	"net/http"
	"strconv"
	"strings"

	"github.com/h2non/bimg"
	"github.com/h2non/filetype"
)

func indexController(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		ErrorReply(r, w, ErrNotFound, ServerOptions{})
		return
	}

	body, _ := json.Marshal(Versions{
		Version,
		bimg.Version,
		bimg.VipsVersion,
	})
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(body)
}

func healthController(w http.ResponseWriter, r *http.Request) {
	health := GetHealthStats()
	body, _ := json.Marshal(health)
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(body)
}

func imageController(o ServerOptions, operation Operation) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		var imageSource = MatchSource(req)
		if imageSource == nil {
			ErrorReply(req, w, ErrMissingImageSource, o)
			return
		}

		buf, err := imageSource.GetImage(req)
		if err != nil {
			if xerr, ok := err.(Error); ok {
				ErrorReply(req, w, xerr, o)
			} else {
				ErrorReply(req, w, NewError(err.Error(), http.StatusBadRequest), o)
			}
			return
		}

		if len(buf) == 0 {
			ErrorReply(req, w, ErrEmptyBody, o)
			return
		}

		imageHandler(w, req, buf, operation, o)
	}
}

func determineAcceptMimeType(accept string) string {
	for _, v := range strings.Split(accept, ",") {
		mediaType, _, _ := mime.ParseMediaType(v)
		switch mediaType {
		case "image/webp":
			return "webp"
		case "image/png":
			return "png"
		case "image/jpeg":
			return "jpeg"
		}
	}

	return ""
}

func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, operation Operation, o ServerOptions) {
	// Infer the body MIME type via mime sniff algorithm
	mimeType := http.DetectContentType(buf)

	// If cannot infer the type, infer it via magic numbers
	if mimeType == "application/octet-stream" {
		kind, err := filetype.Get(buf)
		if err == nil && kind.MIME.Value != "" {
			mimeType = kind.MIME.Value
		}
	}

	// Infer text/plain responses as potential SVG image
	if strings.Contains(mimeType, "text/plain") && len(buf) > 8 {
		if bimg.IsSVGImage(buf) {
			mimeType = "image/svg+xml"
		}
	}

	// Finally check if image MIME type is supported
	if !IsImageMimeTypeSupported(mimeType) {
		ErrorReply(r, w, ErrUnsupportedMedia, o)
		return
	}

	opts, err := buildParamsFromQuery(r.URL.Query())
	if err != nil {
		ErrorReply(r, w, NewError("Error while processing parameters, "+err.Error(), http.StatusBadRequest), o)
		return
	}

	vary := ""
	if opts.Type == "auto" {
		opts.Type = determineAcceptMimeType(r.Header.Get("Accept"))
		vary = "Accept" // Ensure caches behave correctly for negotiated content
	} else if opts.Type != "" && ImageType(opts.Type) == 0 {
		ErrorReply(r, w, ErrOutputFormat, o)
		return
	}

	image, err := operation.Run(buf, opts)
	if err != nil {
		ErrorReply(r, w, NewError("Error while processing the image: "+err.Error(), http.StatusBadRequest), o)
		return
	}

	// Expose Content-Length response header
	w.Header().Set("Content-Length", strconv.Itoa(len(image.Body)))
	w.Header().Set("Content-Type", image.Mime)
	if vary != "" {
		w.Header().Set("Vary", vary)
	}
	_, _ = w.Write(image.Body)
}

func formController(w http.ResponseWriter, r *http.Request) {
	operations := []struct {
		name   string
		method string
		args   string
	}{
		{"Resize", "resize", "width=300&height=200&type=jpeg"},
		{"Force resize", "resize", "width=300&height=200&force=true"},
		{"Crop", "crop", "width=300&quality=95"},
		{"SmartCrop", "crop", "width=300&height=260&quality=95&gravity=smart"},
		{"Extract", "extract", "top=100&left=100&areawidth=300&areaheight=150"},
		{"Enlarge", "enlarge", "width=1440&height=900&quality=95"},
		{"Rotate", "rotate", "rotate=180"},
		{"AutoRotate", "autorotate", "quality=90"},
		{"Flip", "flip", ""},
		{"Flop", "flop", ""},
		{"Thumbnail", "thumbnail", "width=100"},
		{"Zoom", "zoom", "factor=2&areawidth=300&top=80&left=80"},
		{"Color space (black&white)", "resize", "width=400&height=300&colorspace=bw"},
		{"Add watermark", "watermark", "textwidth=100&text=Hello&font=sans%2012&opacity=0.5&color=255,200,50"},
		{"Convert format", "convert", "type=png"},
		{"Image metadata", "info", ""},
		{"Gaussian blur", "blur", "sigma=15.0&minampl=0.2"},
		{"Pipeline (image reduction via multiple transformations)", "pipeline", "operations=%5B%7B%22operation%22:%20%22crop%22,%20%22params%22:%20%7B%22width%22:%20300,%20%22height%22:%20260%7D%7D,%20%7B%22operation%22:%20%22convert%22,%20%22params%22:%20%7B%22type%22:%20%22webp%22%7D%7D%5D"},
	}

	html := ""

	for _, form := range operations {
		html += fmt.Sprintf(`

%s

    <form method="POST" action="/%s?%s" enctype="multipart/form-data">
      <input type="file" name="file" />
      <input type="submit" value="Upload" />
    </form>`, form.name, form.method, form.args)
	}

	html += ""

	w.Header().Set("Content-Type", "text/html")
	_, _ = w.Write([]byte(html))
}
<commit_msg>Also set the vary header when an error occurs (#329)<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"mime"
	"net/http"
	"strconv"
	"strings"

	"github.com/h2non/bimg"
	"github.com/h2non/filetype"
)

func indexController(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		ErrorReply(r, w, ErrNotFound, ServerOptions{})
		return
	}

	body, _ := json.Marshal(Versions{
		Version,
		bimg.Version,
		bimg.VipsVersion,
	})
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(body)
}

func healthController(w http.ResponseWriter, r *http.Request) {
	health := GetHealthStats()
	body, _ := json.Marshal(health)
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(body)
}

func imageController(o ServerOptions, operation Operation) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		var imageSource = MatchSource(req)
		if imageSource == nil {
			ErrorReply(req, w, ErrMissingImageSource, o)
			return
		}

		buf, err := imageSource.GetImage(req)
		if err != nil {
			if xerr, ok := err.(Error); ok {
				ErrorReply(req, w, xerr, o)
			} else {
				ErrorReply(req, w, NewError(err.Error(), http.StatusBadRequest), o)
			}
			return
		}

		if len(buf) == 0 {
			ErrorReply(req, w, ErrEmptyBody, o)
			return
		}

		imageHandler(w, req, buf, operation, o)
	}
}

func determineAcceptMimeType(accept string) string {
	for _, v := range strings.Split(accept, ",") {
		mediaType, _, _ := mime.ParseMediaType(v)
		switch mediaType {
		case "image/webp":
			return "webp"
		case "image/png":
			return "png"
		case "image/jpeg":
			return "jpeg"
		}
	}

	return ""
}

func imageHandler(w http.ResponseWriter, r *http.Request, buf []byte, operation Operation, o ServerOptions) {
	// Infer the body MIME type via mime sniff algorithm
	mimeType := http.DetectContentType(buf)

	// If cannot infer the type, infer it via magic numbers
	if mimeType == "application/octet-stream" {
		kind, err := filetype.Get(buf)
		if err == nil && kind.MIME.Value != "" {
			mimeType = kind.MIME.Value
		}
	}

	// Infer text/plain responses as potential SVG image
	if strings.Contains(mimeType, "text/plain") && len(buf) > 8 {
		if bimg.IsSVGImage(buf) {
			mimeType = "image/svg+xml"
		}
	}

	// Finally check if image MIME type is supported
	if !IsImageMimeTypeSupported(mimeType) {
		ErrorReply(r, w, ErrUnsupportedMedia, o)
		return
	}

	opts, err := buildParamsFromQuery(r.URL.Query())
	if err != nil {
		ErrorReply(r, w, NewError("Error while processing parameters, "+err.Error(), http.StatusBadRequest), o)
		return
	}

	vary := ""
	if opts.Type == "auto" {
		opts.Type = determineAcceptMimeType(r.Header.Get("Accept"))
		vary = "Accept" // Ensure caches behave correctly for negotiated content
	} else if opts.Type != "" && ImageType(opts.Type) == 0 {
		ErrorReply(r, w, ErrOutputFormat, o)
		return
	}

	image, err := operation.Run(buf, opts)
	if err != nil {
		// Ensure the Vary header is set when an error occurs
		if vary != "" {
			w.Header().Set("Vary", vary)
		}
		ErrorReply(r, w, NewError("Error while processing the image: "+err.Error(), http.StatusBadRequest), o)
		return
	}

	// Expose Content-Length response header
	w.Header().Set("Content-Length", strconv.Itoa(len(image.Body)))
	w.Header().Set("Content-Type", image.Mime)
	if vary != "" {
		w.Header().Set("Vary", vary)
	}
	_, _ = w.Write(image.Body)
}

func formController(w http.ResponseWriter, r *http.Request) {
	operations := []struct {
		name   string
		method string
		args   string
	}{
		{"Resize", "resize", "width=300&height=200&type=jpeg"},
		{"Force resize", "resize", "width=300&height=200&force=true"},
		{"Crop", "crop", "width=300&quality=95"},
		{"SmartCrop", "crop", "width=300&height=260&quality=95&gravity=smart"},
		{"Extract", "extract", "top=100&left=100&areawidth=300&areaheight=150"},
		{"Enlarge", "enlarge", "width=1440&height=900&quality=95"},
		{"Rotate", "rotate", "rotate=180"},
		{"AutoRotate", "autorotate", "quality=90"},
		{"Flip", "flip", ""},
		{"Flop", "flop", ""},
		{"Thumbnail", "thumbnail", "width=100"},
		{"Zoom", "zoom", "factor=2&areawidth=300&top=80&left=80"},
		{"Color space (black&white)", "resize", "width=400&height=300&colorspace=bw"},
		{"Add watermark", "watermark", "textwidth=100&text=Hello&font=sans%2012&opacity=0.5&color=255,200,50"},
		{"Convert format", "convert", "type=png"},
		{"Image metadata", "info", ""},
		{"Gaussian blur", "blur", "sigma=15.0&minampl=0.2"},
		{"Pipeline (image reduction via multiple transformations)", "pipeline", "operations=%5B%7B%22operation%22:%20%22crop%22,%20%22params%22:%20%7B%22width%22:%20300,%20%22height%22:%20260%7D%7D,%20%7B%22operation%22:%20%22convert%22,%20%22params%22:%20%7B%22type%22:%20%22webp%22%7D%7D%5D"},
	}

	html := ""

	for _, form := range operations {
		html += fmt.Sprintf(`

%s

    <form method="POST" action="/%s?%s" enctype="multipart/form-data">
      <input type="file" name="file" />
      <input type="submit" value="Upload" />
    </form>`, form.name, form.method, form.args)
	}

	html += ""

	w.Header().Set("Content-Type", "text/html")
	_, _ = w.Write([]byte(html))
}
<|endoftext|>


<commit_before>package kasper

type Coordinator interface {
	Commit() error
	Shutdown() error
}
<commit_msg>Renaming<commit_after>package kasper

type Coordinator interface {
	MarkOffsets() error
	Shutdown() error
}
<|endoftext|>


<commit_before>package handler

import (
	"github.com/Comcast/webpa-common/health"
	"net/http"
)

const (
	TotalRequestsReceived            health.Stat = "TotalRequestsReceived"
	TotalRequestSuccessfullyServiced health.Stat = "TotalRequestSuccessfullyServiced"
	TotalRequestDenied               health.Stat = "TotalRequestDenied"
)

// healthEventSink is the internal, expected interface that health request events are dispatched to
type healthEventSink interface {
	SendEvent(health.HealthFunc)
}

// NewHealthRequestListener returns a new RequestListener which dispatches request stats
func NewHealthRequestListener(sink healthEventSink) RequestListener {
	return &healthRequestListener{sink}
}

// healthRequestListener is a handler.RequestListener that updates the health-related stats.
type healthRequestListener struct {
	sink healthEventSink
}

func (listener *healthRequestListener) RequestReceived(request *http.Request) {
	listener.sink.SendEvent(
		health.Inc(TotalRequestsReceived, 1),
	)
}

func (listener *healthRequestListener) RequestCompleted(statusCode int, request *http.Request) {
	if statusCode < 400 {
		listener.sink.SendEvent(
			health.Inc(TotalRequestSuccessfullyServiced, 1),
		)
	} else {
		listener.sink.SendEvent(
			health.Inc(TotalRequestDenied, 1),
		)
	}
}
<commit_msg>Fixed the strange panic when doing go install petasos<commit_after>package handler

import (
	"github.com/Comcast/webpa-common/health"
	"net/http"
)

const (
	TotalRequestsReceived            health.Stat = "TotalRequestsReceived"
	TotalRequestSuccessfullyServiced health.Stat = "TotalRequestSuccessfullyServiced"
	TotalRequestDenied               health.Stat = "TotalRequestDenied"
)

// healthEventSink is the internal, expected interface that health request events are dispatched to
type healthEventSink interface {
	SendEvent(health.HealthFunc)
}

// healthRequestListener is a handler.RequestListener that updates the health-related stats.
type healthRequestListener struct {
	sink healthEventSink
}

// NewHealthRequestListener returns a new RequestListener which dispatches request stats
func NewHealthRequestListener(sink healthEventSink) RequestListener {
	return &healthRequestListener{sink}
}

func (listener *healthRequestListener) RequestReceived(request *http.Request) {
	listener.sink.SendEvent(
		health.Inc(TotalRequestsReceived, 1),
	)
}

func (listener *healthRequestListener) RequestCompleted(statusCode int, request *http.Request) {
	if statusCode < 400 {
		listener.sink.SendEvent(
			health.Inc(TotalRequestSuccessfullyServiced, 1),
		)
	} else {
		listener.sink.SendEvent(
			health.Inc(TotalRequestDenied, 1),
		)
	}
}
<|endoftext|>


<commit_before>package pqt_test

import (
	"testing"

	"github.com/piotrkowalczuk/pqt"
)

func TestNewTable(t *testing.T) {
	tbl := pqt.NewTable("test", pqt.WithIfNotExists(), pqt.WithTableSpace("table_space"), pqt.WithTemporary())

	if !tbl.IfNotExists {
		t.Errorf("table should have field if not exists set to true")
	}

	if !tbl.Temporary {
		t.Errorf("table should have field temporary set to true")
	}

	if tbl.TableSpace != "table_space" {
		t.Errorf("table should have field table space set to table_space")
	}
}

func TestTable_AddColumn(t *testing.T) {
	c0 := pqt.NewColumn("c0", pqt.TypeSerialBig(), pqt.WithPrimaryKey())
	c1 := &pqt.Column{Name: "c1"}
	c2 := &pqt.Column{Name: "c2"}
	c3 := &pqt.Column{Name: "c3"}

	tbl := pqt.NewTable("test").
		AddColumn(c0).
		AddColumn(c1).
		AddColumn(c2).
		AddColumn(c3).
		AddColumn(pqt.NewColumn("c4", pqt.TypeIntegerBig(), pqt.WithReference(c0))).
		AddRelationship(pqt.ManyToOne(pqt.SelfReference()))

	if len(tbl.Columns) != 6 {
		t.Errorf("wrong number of colums, expected %d but got %d", 6, len(tbl.Columns))
	}

	if len(tbl.OwnedRelationships) != 2 {
		t.Errorf("wrong number of owned relationships, expected %d but got %d", 2, len(tbl.OwnedRelationships))
	}

	for i, c := range tbl.Columns {
		if c.Name == "" {
			t.Errorf("column #%d table name is empty", i)
		}
		if c.Table == nil {
			t.Errorf("column #%d table nil pointer", i)
		}
	}
}

func TestTable_AddRelationship_oneToOneBidirectional(t *testing.T) {
	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
	userDetail := pqt.NewTable("user_detail").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))

	user.AddRelationship(pqt.OneToOne(
		userDetail,
		pqt.WithInversedName("details"),
		pqt.WithOwnerName("user"),
		pqt.WithBidirectional(),
	))

	if len(user.OwnedRelationships) != 1 {
		t.Fatalf("user should have 1 relationship, but has %d", len(user.OwnedRelationships))
	}

	if user.OwnedRelationships[0].OwnerName != "user" {
		t.Errorf("user relationship to user_detail should be mapped by user, but is %s", user.OwnedRelationships[0].OwnerName)
	}

	if user.OwnedRelationships[0].OwnerTable != user {
		t.Errorf("user relationship to user_detail should be mapped by user table, but is %s", user.OwnedRelationships[0].OwnerTable)
	}

	if user.OwnedRelationships[0].Type != pqt.RelationshipTypeOneToOne {
		t.Errorf("user relationship to user_detail should be one to one bidirectional")
	}

	if len(userDetail.InversedRelationships) != 1 {
		t.Fatalf("user_detail should have 1 relationship, but has %d", len(userDetail.InversedRelationships))
	}

	if userDetail.InversedRelationships[0].InversedName != "details" {
		t.Errorf("user_detail relationship to user should be mapped by user")
	}

	if userDetail.InversedRelationships[0].InversedTable != userDetail {
		t.Errorf("user_detail relationship to user should be mapped by user_detail table")
	}

	if userDetail.InversedRelationships[0].Type != pqt.RelationshipTypeOneToOne {
		t.Errorf("user_detail relationship to user should be %d, but is %d", pqt.RelationshipTypeOneToOne, userDetail.InversedRelationships[0].Type)
	}
}

func TestTable_AddRelationship_oneToOneUnidirectional(t *testing.T) {
	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
	userDetail := pqt.NewTable("user_detail").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey())).
		AddRelationship(pqt.OneToOne(
			user,
			pqt.WithInversedName("user"),
			pqt.WithOwnerName("details"),
		))

	if len(user.InversedRelationships) != 0 {
		t.Fatalf("user should have 0 relationship, but has %d", len(user.InversedRelationships))
	}

	if len(userDetail.OwnedRelationships) != 1 {
		t.Fatalf("user_detail should have 1 relationship, but has %d", len(userDetail.OwnedRelationships))
	}

	if userDetail.OwnedRelationships[0].InversedName != "user" {
		t.Errorf("user_detail relationship to user should be mapped by user")
	}

	if userDetail.OwnedRelationships[0].InversedTable != user {
		t.Errorf("user_detail relationship to user should be mapped by user table")
	}

	if userDetail.OwnedRelationships[0].Type != pqt.RelationshipTypeOneToOne {
		t.Errorf("user_detail relationship to user should be %d, but is %d", pqt.RelationshipTypeOneToOne, userDetail.OwnedRelationships[0].Type)
	}
}

func TestTable_AddRelationship_oneToOneSelfReferencing(t *testing.T) {
	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))

	user.AddRelationship(pqt.OneToOne(
		pqt.SelfReference(),
		pqt.WithInversedName("child"),
		pqt.WithOwnerName("parent"),
	))

	if len(user.OwnedRelationships) != 1 {
		t.Fatalf("user should have 1 owned relationship, but has %d", len(user.OwnedRelationships))
	}

	if user.OwnedRelationships[0].OwnerName != "parent" {
		t.Errorf("user relationship to user should be mapped by parent")
	}

	if user.OwnedRelationships[0].OwnerTable != user {
		t.Errorf("user relationship to user should be mapped by user table")
	}

	if user.OwnedRelationships[0].Type != pqt.RelationshipTypeOneToOne {
		t.Errorf("user relationship to user should be %d, but is %d", pqt.RelationshipTypeOneToOne, user.OwnedRelationships[0].Type)
	}

	if len(user.InversedRelationships) != 0 {
		t.Fatalf("user should have 0 inversed relationship, but has %d", len(user.InversedRelationships))
	}
}

func TestTable_AddRelationship_oneToMany(t *testing.T) {
	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
	comment := pqt.NewTable("comment").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))

	user.AddRelationship(pqt.OneToMany(
		comment,
		pqt.WithBidirectional(),
		pqt.WithInversedName("author"),
		pqt.WithOwnerName("comments"),
	))

	if len(user.InversedRelationships) != 1 {
		t.Fatalf("user should have 1 inversed relationship, but has %d", len(user.InversedRelationships))
	}

	if user.InversedRelationships[0].OwnerName != "comments" {
		t.Errorf("user inversed relationship to comment should be mapped by comments")
	}

	if user.InversedRelationships[0].OwnerTable != comment {
		t.Errorf("user inversed relationship to comment should be mapped by comment table")
	}

	if user.InversedRelationships[0].Type != pqt.RelationshipTypeOneToMany {
		t.Errorf("user inversed relationship to comment should be one to many")
	}

	if len(comment.OwnedRelationships) != 1 {
		t.Fatalf("comment should have 1 owned relationship, but has %d", len(comment.OwnedRelationships))
	}

	if comment.OwnedRelationships[0].InversedName != "author" {
		t.Errorf("comment relationship to user should be mapped by author")
	}

	if comment.OwnedRelationships[0].InversedTable != user {
		t.Errorf("comment relationship to user should be mapped by user table")
	}

	if comment.OwnedRelationships[0].Type != pqt.RelationshipTypeOneToMany {
		t.Errorf("comment relationship to user should be %d, but is %d", pqt.RelationshipTypeOneToMany, comment.OwnedRelationships[0].Type)
	}
}

//func TestTable_AddRelationship_manyToMany(t *testing.T) {
//	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
//	group := pqt.NewTable("group").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
//	userGroups := pqt.NewTable("user_groups")
//	user.AddRelationship(pqt.ManyToMany(
//		group,
//		userGroups,
//		pqt.WithInversedName("users"),
//		pqt.WithOwnerName("groups"),
//	))
//
//	if len(user.Relationships) != 1 {
//		t.Fatalf("user should have 1 relationship, but has %d", len(user.Relationships))
//	}
//
//	if user.Relationships[0].OwnerName != "groups" {
//		t.Errorf("user relationship to group should be mapped by groups")
//	}
//
//	if user.Relationships[0].OwnerTable != group {
//		t.Errorf("user relationship to group should be mapped by group table")
//	}
//
//	if user.Relationships[0].Type != pqt.RelationshipTypeManyToMany {
//		t.Errorf("user relationship to group should be many to many")
//	}
//
//	if len(group.Relationships) != 1 {
//		t.Fatalf("group should have 1 relationship, but has %d", len(group.Relationships))
//	}
//
//	if group.Relationships[0].InversedName != "users" {
//		t.Errorf("group relationship to user should be mapped by users")
//	}
//
//	if group.Relationships[0].InversedTable != user {
//		t.Errorf("group relationship to user should be mapped by user table")
//	}
//
//	if group.Relationships[0].Type != pqt.RelationshipTypeManyToMany {
//		t.Errorf("group relationship to user should be %d, but is %d", pqt.RelationshipTypeManyToMany, group.Relationships[0].Type)
//	}
//}
//
//func TestTable_AddRelationship_manyToManySelfReferencing(t *testing.T) {
//	friendship := pqt.NewTable("friendship")
//	user := pqt.NewTable("user").
//		AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey())).
//		AddRelationship(pqt.ManyToManySelfReferencing(
//		friendship,
//		pqt.WithInversedName("friends_with_me"),
//		pqt.WithOwnerName("my_friends"),
//	))
//
//	if len(user.Relationships) != 2 {
//		t.Fatalf("user should have 2 relationships, but has %d", len(user.Relationships))
//	}
//
//	if user.Relationships[0].OwnerName != "my_friends" {
//		t.Errorf("user relationship to user should be mapped by my_friends")
//	}
//
//	if user.Relationships[0].OwnerTable != user {
//		t.Errorf("user relationship to group should be mapped by group table")
//	}
//
//	if user.Relationships[0].Type != pqt.RelationshipTypeManyToManySelfReferencing {
//		t.Errorf("user relationship to group should be many to many")
//	}
//
//	if user.Relationships[1].InversedName != "friends_with_me" {
//		t.Errorf("user relationship to user should be mapped by friends_with_me")
//	}
//
//	if user.Relationships[1].InversedTable != user {
//		t.Errorf("user relationship to user should be mapped by user table")
//	}
//
//	if user.Relationships[1].Type != pqt.RelationshipTypeManyToManySelfReferencing {
//		t.Errorf("user relationship to user should be %d, but is %d", pqt.RelationshipTypeManyToManySelfReferencing, user.Relationships[1].Type)
//	}
//}
<commit_msg>AddColumn test fix<commit_after>package pqt_test

import (
	"testing"

	"github.com/piotrkowalczuk/pqt"
)

func TestNewTable(t *testing.T) {
	tbl := pqt.NewTable("test", pqt.WithIfNotExists(), pqt.WithTableSpace("table_space"), pqt.WithTemporary())

	if !tbl.IfNotExists {
		t.Errorf("table should have field if not exists set to true")
	}

	if !tbl.Temporary {
		t.Errorf("table should have field temporary set to true")
	}

	if tbl.TableSpace != "table_space" {
		t.Errorf("table should have field table space set to table_space")
	}
}

func TestTable_AddColumn(t *testing.T) {
	c0 := pqt.NewColumn("c0", pqt.TypeSerialBig(), pqt.WithPrimaryKey())
	c1 := &pqt.Column{Name: "c1"}
	c2 := &pqt.Column{Name: "c2"}
	c3 := &pqt.Column{Name: "c3"}

	tbl := pqt.NewTable("test").
		AddColumn(c0).
		AddColumn(c1).
		AddColumn(c2).
		AddColumn(c3).
		AddColumn(pqt.NewColumn("c4", pqt.TypeIntegerBig(), pqt.WithReference(c0))).
		AddRelationship(pqt.ManyToOne(pqt.SelfReference()))

	if len(tbl.Columns) != 6 {
		t.Errorf("wrong number of colums, expected %d but got %d", 6, len(tbl.Columns))
	}

	if len(tbl.OwnedRelationships) != 1 {
		// Reference is not a relationship
		t.Errorf("wrong number of owned relationships, expected %d but got %d", 1, len(tbl.OwnedRelationships))
	}

	for i, c := range tbl.Columns {
		if c.Name == "" {
			t.Errorf("column #%d table name is empty", i)
		}
		if c.Table == nil {
			t.Errorf("column #%d table nil pointer", i)
		}
	}
}

func TestTable_AddRelationship_oneToOneBidirectional(t *testing.T) {
	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
	userDetail := pqt.NewTable("user_detail").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))

	user.AddRelationship(pqt.OneToOne(
		userDetail,
		pqt.WithInversedName("details"),
		pqt.WithOwnerName("user"),
		pqt.WithBidirectional(),
	))

	if len(user.OwnedRelationships) != 1 {
		t.Fatalf("user should have 1 relationship, but has %d", len(user.OwnedRelationships))
	}

	if user.OwnedRelationships[0].OwnerName != "user" {
		t.Errorf("user relationship to user_detail should be mapped by user, but is %s", user.OwnedRelationships[0].OwnerName)
	}

	if user.OwnedRelationships[0].OwnerTable != user {
		t.Errorf("user relationship to user_detail should be mapped by user table, but is %s", user.OwnedRelationships[0].OwnerTable)
	}

	if user.OwnedRelationships[0].Type != pqt.RelationshipTypeOneToOne {
		t.Errorf("user relationship to user_detail should be one to one bidirectional")
	}

	if len(userDetail.InversedRelationships) != 1 {
		t.Fatalf("user_detail should have 1 relationship, but has %d", len(userDetail.InversedRelationships))
	}

	if userDetail.InversedRelationships[0].InversedName != "details" {
		t.Errorf("user_detail relationship to user should be mapped by user")
	}

	if userDetail.InversedRelationships[0].InversedTable != userDetail {
		t.Errorf("user_detail relationship to user should be mapped by user_detail table")
	}

	if userDetail.InversedRelationships[0].Type != pqt.RelationshipTypeOneToOne {
		t.Errorf("user_detail relationship to user should be %d, but is %d", pqt.RelationshipTypeOneToOne, userDetail.InversedRelationships[0].Type)
	}
}

func TestTable_AddRelationship_oneToOneUnidirectional(t *testing.T) {
	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
	userDetail := pqt.NewTable("user_detail").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey())).
		AddRelationship(pqt.OneToOne(
			user,
			pqt.WithInversedName("user"),
			pqt.WithOwnerName("details"),
		))

	if len(user.InversedRelationships) != 0 {
		t.Fatalf("user should have 0 relationship, but has %d", len(user.InversedRelationships))
	}

	if len(userDetail.OwnedRelationships) != 1 {
		t.Fatalf("user_detail should have 1 relationship, but has %d", len(userDetail.OwnedRelationships))
	}

	if userDetail.OwnedRelationships[0].InversedName != "user" {
		t.Errorf("user_detail relationship to user should be mapped by user")
	}

	if userDetail.OwnedRelationships[0].InversedTable != user {
		t.Errorf("user_detail relationship to user should be mapped by user table")
	}

	if userDetail.OwnedRelationships[0].Type != pqt.RelationshipTypeOneToOne {
		t.Errorf("user_detail relationship to user should be %d, but is %d", pqt.RelationshipTypeOneToOne, userDetail.OwnedRelationships[0].Type)
	}
}

func TestTable_AddRelationship_oneToOneSelfReferencing(t *testing.T) {
	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))

	user.AddRelationship(pqt.OneToOne(
		pqt.SelfReference(),
		pqt.WithInversedName("child"),
		pqt.WithOwnerName("parent"),
	))

	if len(user.OwnedRelationships) != 1 {
		t.Fatalf("user should have 1 owned relationship, but has %d", len(user.OwnedRelationships))
	}

	if user.OwnedRelationships[0].OwnerName != "parent" {
		t.Errorf("user relationship to user should be mapped by parent")
	}

	if user.OwnedRelationships[0].OwnerTable != user {
		t.Errorf("user relationship to user should be mapped by user table")
	}

	if user.OwnedRelationships[0].Type != pqt.RelationshipTypeOneToOne {
		t.Errorf("user relationship to user should be %d, but is %d", pqt.RelationshipTypeOneToOne, user.OwnedRelationships[0].Type)
	}

	if len(user.InversedRelationships) != 0 {
		t.Fatalf("user should have 0 inversed relationship, but has %d", len(user.InversedRelationships))
	}
}

func TestTable_AddRelationship_oneToMany(t *testing.T) {
	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
	comment := pqt.NewTable("comment").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))

	user.AddRelationship(pqt.OneToMany(
		comment,
		pqt.WithBidirectional(),
		pqt.WithInversedName("author"),
		pqt.WithOwnerName("comments"),
	))

	if len(user.InversedRelationships) != 1 {
		t.Fatalf("user should have 1 inversed relationship, but has %d", len(user.InversedRelationships))
	}

	if user.InversedRelationships[0].OwnerName != "comments" {
		t.Errorf("user inversed relationship to comment should be mapped by comments")
	}

	if user.InversedRelationships[0].OwnerTable != comment {
		t.Errorf("user inversed relationship to comment should be mapped by comment table")
	}

	if user.InversedRelationships[0].Type != pqt.RelationshipTypeOneToMany {
		t.Errorf("user inversed relationship to comment should be one to many")
	}

	if len(comment.OwnedRelationships) != 1 {
		t.Fatalf("comment should have 1 owned relationship, but has %d", len(comment.OwnedRelationships))
	}

	if comment.OwnedRelationships[0].InversedName != "author" {
		t.Errorf("comment relationship to user should be mapped by author")
	}

	if comment.OwnedRelationships[0].InversedTable != user {
		t.Errorf("comment relationship to user should be mapped by user table")
	}

	if comment.OwnedRelationships[0].Type != pqt.RelationshipTypeOneToMany {
		t.Errorf("comment relationship to user should be %d, but is %d", pqt.RelationshipTypeOneToMany, comment.OwnedRelationships[0].Type)
	}
}

//func TestTable_AddRelationship_manyToMany(t *testing.T) {
//	user := pqt.NewTable("user").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
//	group := pqt.NewTable("group").AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey()))
//	userGroups := pqt.NewTable("user_groups")
//	user.AddRelationship(pqt.ManyToMany(
//		group,
//		userGroups,
//		pqt.WithInversedName("users"),
//		pqt.WithOwnerName("groups"),
//	))
//
//	if len(user.Relationships) != 1 {
//		t.Fatalf("user should have 1 relationship, but has %d", len(user.Relationships))
//	}
//
//	if user.Relationships[0].OwnerName != "groups" {
//		t.Errorf("user relationship to group should be mapped by groups")
//	}
//
//	if user.Relationships[0].OwnerTable != group {
//		t.Errorf("user relationship to group should be mapped by group table")
//	}
//
//	if user.Relationships[0].Type != pqt.RelationshipTypeManyToMany {
//		t.Errorf("user relationship to group should be many to many")
//	}
//
//	if len(group.Relationships) != 1 {
//		t.Fatalf("group should have 1 relationship, but has %d", len(group.Relationships))
//	}
//
//	if group.Relationships[0].InversedName != "users" {
//		t.Errorf("group relationship to user should be mapped by users")
//	}
//
//	if group.Relationships[0].InversedTable != user {
//		t.Errorf("group relationship to user should be mapped by user table")
//	}
//
//	if group.Relationships[0].Type != pqt.RelationshipTypeManyToMany {
//		t.Errorf("group relationship to user should be %d, but is %d", pqt.RelationshipTypeManyToMany, group.Relationships[0].Type)
//	}
//}
//
//func TestTable_AddRelationship_manyToManySelfReferencing(t *testing.T) {
//	friendship := pqt.NewTable("friendship")
//	user := pqt.NewTable("user").
//		AddColumn(pqt.NewColumn("id", pqt.TypeSerial(), pqt.WithPrimaryKey())).
//		AddRelationship(pqt.ManyToManySelfReferencing(
//		friendship,
//		pqt.WithInversedName("friends_with_me"),
//		pqt.WithOwnerName("my_friends"),
//	))
//
//	if len(user.Relationships) != 2 {
//		t.Fatalf("user should have 2 relationships, but has %d", len(user.Relationships))
//	}
//
//	if user.Relationships[0].OwnerName != "my_friends" {
//		t.Errorf("user relationship to user should be mapped by my_friends")
//	}
//
//	if user.Relationships[0].OwnerTable != user {
//		t.Errorf("user relationship to group should be mapped by group table")
//	}
//
//	if user.Relationships[0].Type != pqt.RelationshipTypeManyToManySelfReferencing {
//		t.Errorf("user relationship to group should be many to many")
//	}
//
//	if user.Relationships[1].InversedName != "friends_with_me" {
//		t.Errorf("user relationship to user should be mapped by friends_with_me")
//	}
//
//	if user.Relationships[1].InversedTable != user {
//		t.Errorf("user relationship to user should be mapped by user table")
//	}
//
//	if user.Relationships[1].Type != pqt.RelationshipTypeManyToManySelfReferencing {
//		t.Errorf("user relationship to user should be %d, but is %d", pqt.RelationshipTypeManyToManySelfReferencing, user.Relationships[1].Type)
//	}
//}
<|endoftext|>


<commit_before>// Copyright (C) 2016  The GoHBase Authors.  All rights reserved.
// This file is part of GoHBase.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

// +build integration

package gohbase_test

import (
	"context"
	"io"
	"strings"
	"testing"
	"time"

	"github.com/tsuna/gohbase"
	"github.com/tsuna/gohbase/filter"
	"github.com/tsuna/gohbase/hrpc"
)

// Name of the meta region.
const metaTableName = "hbase:meta"

// Info family
var infoFamily = map[string][]string{
	"info": nil,
}

var cFamilies = map[string]map[string]string{
	"cf":  nil,
	"cf2": nil,
}

func TestCreateTable(t *testing.T) {
	testTableName := "test1_" + getTimestampString()
	t.Log("testTableName=" + testTableName)

	ac := gohbase.NewAdminClient(*host)
	crt := hrpc.NewCreateTable(context.Background(), []byte(testTableName), cFamilies)

	if err := ac.CreateTable(crt); err != nil {
		t.Errorf("CreateTable returned an error: %v", err)
	}

	// check in hbase:meta if there's a region for the table
	c := gohbase.NewClient(*host)
	metaKey := testTableName + ",,"
	keyFilter := filter.NewPrefixFilter([]byte(metaKey))
	scan, err := hrpc.NewScanStr(context.Background(), metaTableName, hrpc.Filters(keyFilter))
	if err != nil {
		t.Fatalf("Failed to create Scan request: %s", err)
	}

	var rsp []*hrpc.Result
	scanner := c.Scan(scan)
	for {
		res, err := scanner.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatal(err)
		}
		rsp = append(rsp, res)
	}
	if len(rsp) != 1 {
		t.Errorf("Meta returned %s rows for prefix '%s' , want 1", len(rsp), metaKey)
	}
}

func TestDisableDeleteTable(t *testing.T) {
	testTableName := "test1_" + getTimestampString()
	t.Log("testTableName=" + testTableName)
	ac := gohbase.NewAdminClient(*host)

	crt := hrpc.NewCreateTable(context.Background(), []byte(testTableName), cFamilies)
	if err := ac.CreateTable(crt); err != nil {
		t.Errorf("CreateTable returned an error: %v", err)
	}

	// disable
	dit := hrpc.NewDisableTable(context.Background(), []byte(testTableName))
	err := ac.DisableTable(dit)
	if err != nil {
		t.Errorf("DisableTable returned an error: %v", err)
	}

	// HBase protobuf will error if testTableName wasn't disabled before deletion
	det := hrpc.NewDeleteTable(context.Background(), []byte(testTableName))
	err = ac.DeleteTable(det)
	if err != nil {
		t.Errorf("DeleteTable returned an error: %v", err)
	}

	// check in hbase:meta if there's a region for the table
	c := gohbase.NewClient(*host)
	metaKey := testTableName + ",,"
	keyFilter := filter.NewPrefixFilter([]byte(metaKey))
	scan, err := hrpc.NewScanStr(context.Background(), metaTableName, hrpc.Filters(keyFilter))
	if err != nil {
		t.Fatalf("Failed to create Scan request: %s", err)
	}

	var rsp []*hrpc.Result
	scanner := c.Scan(scan)
	for {
		res, err := scanner.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatal(err)
		}
		rsp = append(rsp, res)
	}
	if len(rsp) != 0 {
		t.Errorf("Meta returned %d rows for prefix '%s', want 0", len(rsp), metaKey)
	}
}

func TestEnableTable(t *testing.T) {
	testTableName := "test1_" + getTimestampString()
	t.Log("testTableName=" + testTableName)
	ac := gohbase.NewAdminClient(*host)

	crt := hrpc.NewCreateTable(context.Background(), []byte(testTableName), cFamilies)
	if err := ac.CreateTable(crt); err != nil {
		t.Errorf("CreateTable returned an error: %v", err)
	}

	// disable
	dit := hrpc.NewDisableTable(context.Background(), []byte(testTableName))
	err := ac.DisableTable(dit)
	if err != nil {
		t.Errorf("DisableTable returned an error: %v", err)
	}

	et := hrpc.NewEnableTable(context.Background(), []byte(testTableName))
	err = ac.EnableTable(et)
	if err != nil {
		t.Errorf("EnableTable returned an error: %v", err)
	}

	delt := hrpc.NewDeleteTable(context.Background(), []byte(testTableName))
	err = ac.DeleteTable(delt)
	if err == nil || !strings.Contains(err.Error(), "TableNotDisabledException") {
		t.Errorf("DeleteTable should error with TableNotDisabledException, got %s", err)
	}
}

func getTimestampString() string {
	return time.Now().Format("20060102150405")
}
<commit_msg>test: Fix TestCreateTable<commit_after>// Copyright (C) 2016  The GoHBase Authors.  All rights reserved.
// This file is part of GoHBase.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

// +build integration

package gohbase_test

import (
	"context"
	"io"
	"strings"
	"testing"
	"time"

	"github.com/tsuna/gohbase"
	"github.com/tsuna/gohbase/filter"
	"github.com/tsuna/gohbase/hrpc"
)

// Name of the meta region.
const metaTableName = "hbase:meta"

// Info family
var infoFamily = map[string][]string{
	"info": nil,
}

var cFamilies = map[string]map[string]string{
	"cf":  nil,
	"cf2": nil,
}

func TestCreateTable(t *testing.T) {
	testTableName := "test1_" + getTimestampString()
	t.Log("testTableName=" + testTableName)

	ac := gohbase.NewAdminClient(*host)
	crt := hrpc.NewCreateTable(context.Background(), []byte(testTableName), cFamilies)

	if err := ac.CreateTable(crt); err != nil {
		t.Errorf("CreateTable returned an error: %v", err)
	}

	// check in hbase:meta if there's a region for the table
	c := gohbase.NewClient(*host)
	metaKey := testTableName + ","
	keyFilter := filter.NewPrefixFilter([]byte(metaKey))
	scan, err := hrpc.NewScanStr(context.Background(), metaTableName, hrpc.Filters(keyFilter))
	if err != nil {
		t.Fatalf("Failed to create Scan request: %s", err)
	}

	var rsp []*hrpc.Result
	scanner := c.Scan(scan)
	for {
		res, err := scanner.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatal(err)
		}
		rsp = append(rsp, res)
	}
	if len(rsp) != 1 {
		t.Errorf("Meta returned %s rows for prefix '%s' , want 1", len(rsp), metaKey)
	}
}

func TestDisableDeleteTable(t *testing.T) {
	testTableName := "test1_" + getTimestampString()
	t.Log("testTableName=" + testTableName)
	ac := gohbase.NewAdminClient(*host)

	crt := hrpc.NewCreateTable(context.Background(), []byte(testTableName), cFamilies)
	if err := ac.CreateTable(crt); err != nil {
		t.Errorf("CreateTable returned an error: %v", err)
	}

	// disable
	dit := hrpc.NewDisableTable(context.Background(), []byte(testTableName))
	err := ac.DisableTable(dit)
	if err != nil {
		t.Errorf("DisableTable returned an error: %v", err)
	}

	// HBase protobuf will error if testTableName wasn't disabled before deletion
	det := hrpc.NewDeleteTable(context.Background(), []byte(testTableName))
	err = ac.DeleteTable(det)
	if err != nil {
		t.Errorf("DeleteTable returned an error: %v", err)
	}

	// check in hbase:meta if there's a region for the table
	c := gohbase.NewClient(*host)
	metaKey := testTableName + ",,"
	keyFilter := filter.NewPrefixFilter([]byte(metaKey))
	scan, err := hrpc.NewScanStr(context.Background(), metaTableName, hrpc.Filters(keyFilter))
	if err != nil {
		t.Fatalf("Failed to create Scan request: %s", err)
	}

	var rsp []*hrpc.Result
	scanner := c.Scan(scan)
	for {
		res, err := scanner.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			t.Fatal(err)
		}
		rsp = append(rsp, res)
	}
	if len(rsp) != 0 {
		t.Errorf("Meta returned %d rows for prefix '%s', want 0", len(rsp), metaKey)
	}
}

func TestEnableTable(t *testing.T) {
	testTableName := "test1_" + getTimestampString()
	t.Log("testTableName=" + testTableName)
	ac := gohbase.NewAdminClient(*host)

	crt := hrpc.NewCreateTable(context.Background(), []byte(testTableName), cFamilies)
	if err := ac.CreateTable(crt); err != nil {
		t.Errorf("CreateTable returned an error: %v", err)
	}

	// disable
	dit := hrpc.NewDisableTable(context.Background(), []byte(testTableName))
	err := ac.DisableTable(dit)
	if err != nil {
		t.Errorf("DisableTable returned an error: %v", err)
	}

	et := hrpc.NewEnableTable(context.Background(), []byte(testTableName))
	err = ac.EnableTable(et)
	if err != nil {
		t.Errorf("EnableTable returned an error: %v", err)
	}

	delt := hrpc.NewDeleteTable(context.Background(), []byte(testTableName))
	err = ac.DeleteTable(delt)
	if err == nil || !strings.Contains(err.Error(), "TableNotDisabledException") {
		t.Errorf("DeleteTable should error with TableNotDisabledException, got %s", err)
	}
}

func getTimestampString() string {
	return time.Now().Format("20060102150405")
}
<|endoftext|>