<commit_before>package drive

// This file contains test helper functions.  It is recommended to call all the
// public functions in this file in tests specific to each implementation of
// drive.Client.  This helps reduce duplication, and ensures they have
// uniform behavior.

import (
	"bytes"
	"crypto/rand"
	"sync"
	"testing"
	"time"

	"github.com/asjoyner/shade"
)

const chunkSize uint64 = 100 * 256

// TestFileRoundTrip is a helper function, it allocates numFiles random []byte,
// stores them in the provided client as files, retrieves them, and ensures all
// of the files were returned.
func TestFileRoundTrip(t *testing.T, c Client, numFiles uint64) {
	maxFiles := c.GetConfig().MaxFiles
	testFiles := randChunk(numFiles)

	// Populate testFiles into the client
	for stringSum, file := range testFiles {
		if err := c.PutFile([]byte(stringSum), []byte(file)); err != nil {
			t.Fatal("Failed to put test file: ", err)
		}
	}

	// Populate them all again, which should not return an error.
	orderedFiles := make([]string, 0, numFiles)
	for stringSum, file := range testFiles {
		if err := c.PutFile([]byte(stringSum), []byte(file)); err != nil {
			t.Fatal("Failed to put test file a second time: ", err)
		}
		// Make note of the order, for checking LRU behavior.
		orderedFiles = append(orderedFiles, stringSum)

		// The granularity of the local LRU is only 1 second, because it uses
		// mtime to track which files are most recent.  Sleep at the boundary of
		// the files we expect to be kept, so we ensure we know which ones will be
		// kept.
		if maxFiles > 0 && uint64(len(orderedFiles)) == numFiles-maxFiles {
			time.Sleep(1 * time.Second)
		}
	}
	retainedFiles := orderedFiles
	if maxFiles != 0 && maxFiles < numFiles {
		retainedFiles = orderedFiles[numFiles-maxFiles:]
	}

	// Check all the files are returned by ListFiles
	files, err := c.ListFiles()
	if err != nil {
		t.Fatalf("Failed to retrieve file map: %s", err)
	}
	if len(files) < len(retainedFiles) {
		t.Errorf("ListFiles returned too few files:")
		t.Errorf("want: %d, got: %d:", len(retainedFiles), len(files))
	}
	if maxFiles > 0 && uint64(len(files)) > maxFiles {
		t.Errorf("ListFiles returned too many files:")
		t.Errorf("want: %d, got: %d:", len(retainedFiles), len(files))
	}
	// make searching to see which files weren't returned faster
	returnedFiles := make(map[string]bool, len(files))
	for _, sum := range files {
		returnedFiles[string(sum)] = true
	}

	// check that the files returned are the ones expected
	for _, stringSum := range retainedFiles {
		if !returnedFiles[stringSum] {
			t.Errorf("test file not returned: %x", stringSum)
		}
	}

	// Check that the contents of the files can be retrived with GetFile
	for _, stringSum := range retainedFiles {
		returnedChunk, err := c.GetFile([]byte(stringSum))
		if err != nil {
			t.Errorf("Failed to retrieve chunk %x: %s", stringSum, err)
			continue
		}
		if !bytes.Equal(returnedChunk, testFiles[stringSum]) {
			t.Errorf("returned chunk for %x does not match", stringSum)
			//t.Errorf("got %x, want: %x", returnedChunk, testFiles[stringSum])
		}
	}

	for stringSum := range testFiles {
		if err := c.ReleaseFile([]byte(stringSum)); err != nil {
			t.Logf("Test file could not be released: %s", err)
		}
	}
}

// TestChunkRoundTrip allocates numChunks random []byte, stores them in the
// client as chunks, then retrieves each one by its Sum and compares the bytes
// that are returned.
func TestChunkRoundTrip(t *testing.T, c Client, numChunks uint64) {
	maxBytes := c.GetConfig().MaxChunkBytes
	testChunks := randChunk(uint64(numChunks))

	// Make a file out of the chunks
	file := shade.NewFile("testfile")
	i := 0
	for sum := range testChunks {
		chunk := shade.NewChunk()
		chunk.Index = i
		chunk.Sha256 = []byte(sum)
		file.Chunks = append(file.Chunks, chunk)
		i++
	}
	file.LastChunksize = int(chunkSize)

	// Populate test chunks into the client
	for stringSum, chunk := range testChunks {
		err := c.PutChunk([]byte(stringSum), chunk, file)
		if err != nil {
			t.Fatalf("Failed to put chunk %x: %s", stringSum, err)
		} else {
			//t.Logf("Put chunk %x", stringSum)
		}
	}

	// Populate them all again, which should not return an error.
	var firstRetainedChunk = -1 // ignore LRU behavior by default
	if maxBytes > 0 {
		numRetainedChunks := maxBytes / chunkSize
		firstRetainedChunk = len(testChunks) - int(numRetainedChunks)
		//fmt.Println("first retained chunk: ", firstRetainedChunk)
	}
	orderedChunks := make([]string, 0, numChunks)
	for stringSum, chunk := range testChunks {
		err := c.PutChunk([]byte(stringSum), chunk, file)
		if err != nil {
			t.Fatalf("Failed to put test chunk a second time %x: %s", stringSum, err)
		}

		_, err = c.GetChunk([]byte(stringSum), file)
		if err != nil {
			t.Fatalf("Failed to fetch test chunk %x: %s", stringSum, err)
		}

		// Make note of the order, for checking MaxChunkBytes LRU behavior.
		orderedChunks = append(orderedChunks, stringSum)
		// The granularity of the local LRU is only 1 second, because it uses
		// mtime to track which chunks are most recent.  Sleep at the boundary
		// of the chunks we expect to be kept, so we ensure we know which
		// chunks will be kept.
		if maxBytes > 0 && len(orderedChunks) == firstRetainedChunk {
			time.Sleep(1 * time.Second)
		}
	}

	// Get each chunk by its Sum
	for i, stringSum := range orderedChunks {
		returnedChunk, err := c.GetChunk([]byte(stringSum), file)
		// Check that the newest chunks were retained
		if i >= firstRetainedChunk {
			if err != nil {
				t.Errorf("Failed to retrieve chunk %d of %d with sum %x: %s", i, len(orderedChunks), stringSum, err)
				continue
			}
			if !bytes.Equal(returnedChunk, testChunks[stringSum]) {
				t.Errorf("returned chunk for %xdoes not match", stringSum)
				// t.Errorf("got %q, want: %q", string(returnedChunk), string(chunk))
			}
		} else { // Check that the oldest chunks were removed
			_, err := c.GetChunk([]byte(stringSum), file)
			if err == nil {
				t.Errorf("Retrieved chunk %d of %d which should have been expired: %x", i, len(orderedChunks), stringSum)
			}
		}
	}

	for stringSum := range testChunks {
		if err := c.ReleaseChunk([]byte(stringSum)); err != nil {
			t.Logf("Test chunk could not be released: %s", err)
		}
	}
}

// TestParallelRoundTrip calls 4 copies of both test functions in parallel, to
// try to tickle race conditions in the implementation.
func TestParallelRoundTrip(t *testing.T, c Client, n uint64) {
	var wg sync.WaitGroup
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go runAndDone(TestFileRoundTrip, t, c, n, &wg)
		wg.Add(1)
		go runAndDone(TestChunkRoundTrip, t, c, n, &wg)
	}
	wg.Wait()
	return
}

// TestChunkLister allocates numChunks random []byte, stores them in the client
// as chunks, gets a ChunkLister, then iterates the chunks to ensure they are
// all returned.
func TestChunkLister(t *testing.T, c Client, numChunks uint64) {
	testChunks := randChunk(uint64(numChunks))

	// Make a file out of the chunks
	file := shade.NewFile("testfile")
	i := 0
	for sum := range testChunks {
		chunk := shade.NewChunk()
		chunk.Index = i
		chunk.Sha256 = []byte(sum)
		file.Chunks = append(file.Chunks, chunk)
		i++
	}
	file.LastChunksize = int(chunkSize)

	// Populate test chunks into the client
	for stringSum, chunk := range testChunks {
		err := c.PutChunk([]byte(stringSum), chunk, file)
		if err != nil {
			t.Fatalf("Failed to put chunk %x: %s", stringSum, err)
		} else {
			//t.Logf("Put chunk %x", stringSum)
		}
	}

	// List the Chunk sums
	cl := c.NewChunkLister()
	found := make(map[string]struct{}, numChunks)
	for cl.Next() {
		found[string(cl.Sha256())] = struct{}{}
	}
	if cl.Err() != nil {
		t.Fatalf("Failed to list chunk sums: %s", cl.Err())
	}

	if len(found) != len(testChunks) {
		t.Errorf("ListFiles returned the wrong number of files:")
		t.Errorf("want: %d, got: %d:", len(testChunks), len(found))
	}
	for sum := range found {
		if _, ok := testChunks[sum]; !ok {
			t.Errorf("Found extraneous chunk sum: %x", sum)
		}
	}
	for sum := range testChunks {
		if _, ok := found[sum]; !ok {
			t.Errorf("Missing chunk sum: %x", sum)
		}
	}
}

func runAndDone(f func(*testing.T, Client, uint64), t *testing.T, c Client, n uint64, wg *sync.WaitGroup) {
	defer wg.Done()
	f(t, c, n)
}

// Generate some random test chunks
func randChunk(n uint64) map[string][]byte {
	testChunks := make(map[string][]byte, n)
	for i := uint64(0); i < n; i++ {
		c := make([]byte, chunkSize)
		rand.Read(c)
		testChunks[string(shade.Sum(c))] = c
	}
	return testChunks
}
<commit_msg>Release chunks in TestChunkLister, improve err msg<commit_after>package drive

// This file contains test helper functions.  It is recommended to call all the
// public functions in this file in tests specific to each implementation of
// drive.Client.  This helps reduce duplication, and ensures they have
// uniform behavior.

import (
	"bytes"
	"crypto/rand"
	"sync"
	"testing"
	"time"

	"github.com/asjoyner/shade"
)

const chunkSize uint64 = 100 * 256

// TestFileRoundTrip is a helper function, it allocates numFiles random []byte,
// stores them in the provided client as files, retrieves them, and ensures all
// of the files were returned.
func TestFileRoundTrip(t *testing.T, c Client, numFiles uint64) {
	maxFiles := c.GetConfig().MaxFiles
	testFiles := randChunk(numFiles)

	// Populate testFiles into the client
	for stringSum, file := range testFiles {
		if err := c.PutFile([]byte(stringSum), []byte(file)); err != nil {
			t.Fatal("Failed to put test file: ", err)
		}
	}

	// Populate them all again, which should not return an error.
	orderedFiles := make([]string, 0, numFiles)
	for stringSum, file := range testFiles {
		if err := c.PutFile([]byte(stringSum), []byte(file)); err != nil {
			t.Fatal("Failed to put test file a second time: ", err)
		}
		// Make note of the order, for checking LRU behavior.
		orderedFiles = append(orderedFiles, stringSum)

		// The granularity of the local LRU is only 1 second, because it uses
		// mtime to track which files are most recent.  Sleep at the boundary of
		// the files we expect to be kept, so we ensure we know which ones will be
		// kept.
		if maxFiles > 0 && uint64(len(orderedFiles)) == numFiles-maxFiles {
			time.Sleep(1 * time.Second)
		}
	}
	retainedFiles := orderedFiles
	if maxFiles != 0 && maxFiles < numFiles {
		retainedFiles = orderedFiles[numFiles-maxFiles:]
	}

	// Check all the files are returned by ListFiles
	files, err := c.ListFiles()
	if err != nil {
		t.Fatalf("Failed to retrieve file map: %s", err)
	}
	if len(files) < len(retainedFiles) {
		t.Errorf("ListFiles returned too few files:")
		t.Errorf("want: %d, got: %d:", len(retainedFiles), len(files))
	}
	if maxFiles > 0 && uint64(len(files)) > maxFiles {
		t.Errorf("ListFiles returned too many files:")
		t.Errorf("want: %d, got: %d:", len(retainedFiles), len(files))
	}
	// make searching to see which files weren't returned faster
	returnedFiles := make(map[string]bool, len(files))
	for _, sum := range files {
		returnedFiles[string(sum)] = true
	}

	// check that the files returned are the ones expected
	for _, stringSum := range retainedFiles {
		if !returnedFiles[stringSum] {
			t.Errorf("test file not returned: %x", stringSum)
		}
	}

	// Check that the contents of the files can be retrived with GetFile
	for _, stringSum := range retainedFiles {
		returnedChunk, err := c.GetFile([]byte(stringSum))
		if err != nil {
			t.Errorf("Failed to retrieve chunk %x: %s", stringSum, err)
			continue
		}
		if !bytes.Equal(returnedChunk, testFiles[stringSum]) {
			t.Errorf("returned chunk for %x does not match", stringSum)
			//t.Errorf("got %x, want: %x", returnedChunk, testFiles[stringSum])
		}
	}

	for stringSum := range testFiles {
		if err := c.ReleaseFile([]byte(stringSum)); err != nil {
			t.Logf("Test file could not be released: %s", err)
		}
	}
}

// TestChunkRoundTrip allocates numChunks random []byte, stores them in the
// client as chunks, then retrieves each one by its Sum and compares the bytes
// that are returned.
func TestChunkRoundTrip(t *testing.T, c Client, numChunks uint64) {
	maxBytes := c.GetConfig().MaxChunkBytes
	testChunks := randChunk(uint64(numChunks))

	// Make a file out of the chunks
	file := shade.NewFile("testfile")
	i := 0
	for sum := range testChunks {
		chunk := shade.NewChunk()
		chunk.Index = i
		chunk.Sha256 = []byte(sum)
		file.Chunks = append(file.Chunks, chunk)
		i++
	}
	file.LastChunksize = int(chunkSize)

	// Populate test chunks into the client
	for stringSum, chunk := range testChunks {
		err := c.PutChunk([]byte(stringSum), chunk, file)
		if err != nil {
			t.Fatalf("Failed to put chunk %x: %s", stringSum, err)
		} else {
			//t.Logf("Put chunk %x", stringSum)
		}
	}

	// Populate them all again, which should not return an error.
	var firstRetainedChunk = -1 // ignore LRU behavior by default
	if maxBytes > 0 {
		numRetainedChunks := maxBytes / chunkSize
		firstRetainedChunk = len(testChunks) - int(numRetainedChunks)
		//fmt.Println("first retained chunk: ", firstRetainedChunk)
	}
	orderedChunks := make([]string, 0, numChunks)
	for stringSum, chunk := range testChunks {
		err := c.PutChunk([]byte(stringSum), chunk, file)
		if err != nil {
			t.Fatalf("Failed to put test chunk a second time %x: %s", stringSum, err)
		}

		_, err = c.GetChunk([]byte(stringSum), file)
		if err != nil {
			t.Fatalf("Failed to fetch test chunk %x: %s", stringSum, err)
		}

		// Make note of the order, for checking MaxChunkBytes LRU behavior.
		orderedChunks = append(orderedChunks, stringSum)
		// The granularity of the local LRU is only 1 second, because it uses
		// mtime to track which chunks are most recent.  Sleep at the boundary
		// of the chunks we expect to be kept, so we ensure we know which
		// chunks will be kept.
		if maxBytes > 0 && len(orderedChunks) == firstRetainedChunk {
			time.Sleep(1 * time.Second)
		}
	}

	// Get each chunk by its Sum
	for i, stringSum := range orderedChunks {
		returnedChunk, err := c.GetChunk([]byte(stringSum), file)
		// Check that the newest chunks were retained
		if i >= firstRetainedChunk {
			if err != nil {
				t.Errorf("Failed to retrieve chunk %d of %d with sum %x: %s", i, len(orderedChunks), stringSum, err)
				continue
			}
			if !bytes.Equal(returnedChunk, testChunks[stringSum]) {
				t.Errorf("returned chunk for %xdoes not match", stringSum)
				// t.Errorf("got %q, want: %q", string(returnedChunk), string(chunk))
			}
		} else { // Check that the oldest chunks were removed
			_, err := c.GetChunk([]byte(stringSum), file)
			if err == nil {
				t.Errorf("Retrieved chunk %d of %d which should have been expired: %x", i, len(orderedChunks), stringSum)
			}
		}
	}

	for stringSum := range testChunks {
		if err := c.ReleaseChunk([]byte(stringSum)); err != nil {
			t.Logf("Test chunk could not be released: %s", err)
		}
	}
}

// TestParallelRoundTrip calls 4 copies of both test functions in parallel, to
// try to tickle race conditions in the implementation.
func TestParallelRoundTrip(t *testing.T, c Client, n uint64) {
	var wg sync.WaitGroup
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go runAndDone(TestFileRoundTrip, t, c, n, &wg)
		wg.Add(1)
		go runAndDone(TestChunkRoundTrip, t, c, n, &wg)
	}
	wg.Wait()
	return
}

// TestChunkLister allocates numChunks random []byte, stores them in the client
// as chunks, gets a ChunkLister, then iterates the chunks to ensure they are
// all returned.
func TestChunkLister(t *testing.T, c Client, numChunks uint64) {
	testChunks := randChunk(uint64(numChunks))

	// Make a file out of the chunks
	file := shade.NewFile("testfile")
	i := 0
	for sum := range testChunks {
		chunk := shade.NewChunk()
		chunk.Index = i
		chunk.Sha256 = []byte(sum)
		file.Chunks = append(file.Chunks, chunk)
		i++
	}
	file.LastChunksize = int(chunkSize)

	// Populate test chunks into the client
	for stringSum, chunk := range testChunks {
		err := c.PutChunk([]byte(stringSum), chunk, file)
		if err != nil {
			t.Fatalf("Failed to put chunk %x: %s", stringSum, err)
		} else {
			//t.Logf("Put chunk %x", stringSum)
		}
	}

	// List the Chunk sums
	cl := c.NewChunkLister()
	found := make(map[string]struct{}, numChunks)
	for cl.Next() {
		found[string(cl.Sha256())] = struct{}{}
	}
	if cl.Err() != nil {
		t.Fatalf("Failed to list chunk sums: %s", cl.Err())
	}

	if len(found) != len(testChunks) {
		t.Errorf("ChunkLister returned the wrong number of chunks:")
		t.Errorf("want: %d, got: %d:", len(testChunks), len(found))
	}
	for sum := range found {
		if _, ok := testChunks[sum]; !ok {
			t.Errorf("Found extraneous chunk sum: %x", sum)
		}
	}
	for sum := range testChunks {
		if _, ok := found[sum]; !ok {
			t.Errorf("Missing chunk sum: %x", sum)
		}
	}

	for stringSum := range testChunks {
		if err := c.ReleaseChunk([]byte(stringSum)); err != nil {
			t.Logf("Test chunk could not be released: %s", err)
		}
	}
}

func runAndDone(f func(*testing.T, Client, uint64), t *testing.T, c Client, n uint64, wg *sync.WaitGroup) {
	defer wg.Done()
	f(t, c, n)
}

// Generate some random test chunks
func randChunk(n uint64) map[string][]byte {
	testChunks := make(map[string][]byte, n)
	for i := uint64(0); i < n; i++ {
		c := make([]byte, chunkSize)
		rand.Read(c)
		testChunks[string(shade.Sum(c))] = c
	}
	return testChunks
}
<|endoftext|>


<commit_before>package driver

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/blang/semver"
	"github.com/concourse/semver-resource/version"
)

var gitRepoDir string
var privateKeyPath string

var ErrEncryptedKey = errors.New("private keys with passphrases are not supported")

func init() {
	gitRepoDir = filepath.Join(os.TempDir(), "semver-git-repo")
	privateKeyPath = filepath.Join(os.TempDir(), "private-key")
}

type GitDriver struct {
	InitialVersion semver.Version

	URI        string
	Branch     string
	PrivateKey string
	File       string
}

func (driver *GitDriver) Bump(bump version.Bump) (semver.Version, error) {
	err := driver.setUpKey()
	if err != nil {
		return semver.Version{}, err
	}

	var newVersion semver.Version

	for {
		err = driver.setUpRepo()
		if err != nil {
			return semver.Version{}, err
		}

		currentVersion, exists, err := driver.readVersion()
		if err != nil {
			return semver.Version{}, err
		}

		if !exists {
			currentVersion = driver.InitialVersion
		}

		newVersion = bump.Apply(currentVersion)

		wrote, err := driver.writeVersion(newVersion)
		if wrote {
			break
		}
	}

	return newVersion, nil
}

func (driver *GitDriver) Set(newVersion semver.Version) error {
	err := driver.setUpKey()
	if err != nil {
		return err
	}

	for {
		err = driver.setUpRepo()
		if err != nil {
			return err
		}

		wrote, err := driver.writeVersion(newVersion)
		if err != nil {
			return err
		}

		if wrote {
			break
		}
	}

	return nil
}

func (driver *GitDriver) Check(cursor *semver.Version) ([]semver.Version, error) {
	err := driver.setUpKey()
	if err != nil {
		return nil, err
	}

	err = driver.setUpRepo()
	if err != nil {
		return nil, err
	}

	currentVersion, exists, err := driver.readVersion()
	if err != nil {
		return nil, err
	}

	if !exists {
		return []semver.Version{driver.InitialVersion}, nil
	}

	if cursor == nil || currentVersion.GT(*cursor) {
		return []semver.Version{currentVersion}, nil
	}

	return []semver.Version{}, nil
}

func (driver *GitDriver) setUpRepo() error {
	_, err := os.Stat(gitRepoDir)
	if err != nil {
		gitClone := exec.Command("git", "clone", driver.URI, "--branch", driver.Branch, gitRepoDir)
		gitClone.Stdout = os.Stderr
		gitClone.Stderr = os.Stderr
		if err := gitClone.Run(); err != nil {
			return err
		}
	} else {
		gitFetch := exec.Command("git", "fetch", "origin", driver.Branch)
		gitFetch.Dir = gitRepoDir
		gitFetch.Stdout = os.Stderr
		gitFetch.Stderr = os.Stderr
		if err := gitFetch.Run(); err != nil {
			return err
		}
	}

	gitCheckout := exec.Command("git", "reset", "--hard", "origin/"+driver.Branch)
	gitCheckout.Dir = gitRepoDir
	gitCheckout.Stdout = os.Stderr
	gitCheckout.Stderr = os.Stderr
	if err := gitCheckout.Run(); err != nil {
		return err
	}

	return nil
}

func (driver *GitDriver) setUpKey() error {
	if strings.Contains(driver.PrivateKey, "ENCRYPTED") {
		return ErrEncryptedKey
	}

	_, err := os.Stat(privateKeyPath)
	if err != nil {
		if os.IsNotExist(err) {
			err := ioutil.WriteFile(privateKeyPath, []byte(driver.PrivateKey), 0600)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return os.Setenv("GIT_SSH_COMMAND", "ssh -i "+privateKeyPath)
}

func (driver *GitDriver) readVersion() (semver.Version, bool, error) {
	var currentVersionStr string
	versionFile, err := os.Open(filepath.Join(gitRepoDir, driver.File))
	if err != nil {
		if os.IsNotExist(err) {
			return semver.Version{}, false, nil
		}

		return semver.Version{}, false, err
	}

	defer versionFile.Close()

	_, err = fmt.Fscanf(versionFile, "%s", &currentVersionStr)
	if err != nil {
		return semver.Version{}, false, err
	}

	currentVersion, err := semver.Parse(currentVersionStr)
	if err != nil {
		return semver.Version{}, false, err
	}

	return currentVersion, true, nil
}

const falsePushString = "Everything up-to-date"
const pushRejectedString = "[rejected]"
const pushRemoteRejectedString = "[remote rejected]"

func (driver *GitDriver) writeVersion(newVersion semver.Version) (bool, error) {
	err := ioutil.WriteFile(filepath.Join(gitRepoDir, driver.File), []byte(newVersion.String()+"\n"), 0644)
	if err != nil {
		return false, err
	}

	gitAdd := exec.Command("git", "add", driver.File)
	gitAdd.Dir = gitRepoDir
	gitAdd.Stdout = os.Stderr
	gitAdd.Stderr = os.Stderr
	if err := gitAdd.Run(); err != nil {
		return false, err
	}

	gitCommit := exec.Command("git", "commit", "-m", "bump to "+newVersion.String())
	gitCommit.Dir = gitRepoDir
	gitCommit.Stdout = os.Stderr
	gitCommit.Stderr = os.Stderr
	if err := gitCommit.Run(); err != nil {
		return false, err
	}

	gitPush := exec.Command("git", "push", "origin", "HEAD:"+driver.Branch)
	gitPush.Dir = gitRepoDir

	pushOutput, err := gitPush.CombinedOutput()

	if strings.Contains(string(pushOutput), falsePushString) {
		return false, nil
	}

	if strings.Contains(string(pushOutput), pushRejectedString) {
		return false, nil
	}

	if strings.Contains(string(pushOutput), pushRemoteRejectedString) {
		return false, nil
	}

	if err != nil {
		os.Stderr.Write(pushOutput)
		return false, err
	}

	return true, nil
}
<commit_msg>git driver: do no-op if version is same<commit_after>package driver

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/blang/semver"
	"github.com/concourse/semver-resource/version"
)

var gitRepoDir string
var privateKeyPath string

var ErrEncryptedKey = errors.New("private keys with passphrases are not supported")

func init() {
	gitRepoDir = filepath.Join(os.TempDir(), "semver-git-repo")
	privateKeyPath = filepath.Join(os.TempDir(), "private-key")
}

type GitDriver struct {
	InitialVersion semver.Version

	URI        string
	Branch     string
	PrivateKey string
	File       string
}

func (driver *GitDriver) Bump(bump version.Bump) (semver.Version, error) {
	err := driver.setUpKey()
	if err != nil {
		return semver.Version{}, err
	}

	var newVersion semver.Version

	for {
		err = driver.setUpRepo()
		if err != nil {
			return semver.Version{}, err
		}

		currentVersion, exists, err := driver.readVersion()
		if err != nil {
			return semver.Version{}, err
		}

		if !exists {
			currentVersion = driver.InitialVersion
		}

		newVersion = bump.Apply(currentVersion)

		wrote, err := driver.writeVersion(newVersion)
		if wrote {
			break
		}
	}

	return newVersion, nil
}

func (driver *GitDriver) Set(newVersion semver.Version) error {
	err := driver.setUpKey()
	if err != nil {
		return err
	}

	for {
		err = driver.setUpRepo()
		if err != nil {
			return err
		}

		wrote, err := driver.writeVersion(newVersion)
		if err != nil {
			return err
		}

		if wrote {
			break
		}
	}

	return nil
}

func (driver *GitDriver) Check(cursor *semver.Version) ([]semver.Version, error) {
	err := driver.setUpKey()
	if err != nil {
		return nil, err
	}

	err = driver.setUpRepo()
	if err != nil {
		return nil, err
	}

	currentVersion, exists, err := driver.readVersion()
	if err != nil {
		return nil, err
	}

	if !exists {
		return []semver.Version{driver.InitialVersion}, nil
	}

	if cursor == nil || currentVersion.GT(*cursor) {
		return []semver.Version{currentVersion}, nil
	}

	return []semver.Version{}, nil
}

func (driver *GitDriver) setUpRepo() error {
	_, err := os.Stat(gitRepoDir)
	if err != nil {
		gitClone := exec.Command("git", "clone", driver.URI, "--branch", driver.Branch, gitRepoDir)
		gitClone.Stdout = os.Stderr
		gitClone.Stderr = os.Stderr
		if err := gitClone.Run(); err != nil {
			return err
		}
	} else {
		gitFetch := exec.Command("git", "fetch", "origin", driver.Branch)
		gitFetch.Dir = gitRepoDir
		gitFetch.Stdout = os.Stderr
		gitFetch.Stderr = os.Stderr
		if err := gitFetch.Run(); err != nil {
			return err
		}
	}

	gitCheckout := exec.Command("git", "reset", "--hard", "origin/"+driver.Branch)
	gitCheckout.Dir = gitRepoDir
	gitCheckout.Stdout = os.Stderr
	gitCheckout.Stderr = os.Stderr
	if err := gitCheckout.Run(); err != nil {
		return err
	}

	return nil
}

func (driver *GitDriver) setUpKey() error {
	if strings.Contains(driver.PrivateKey, "ENCRYPTED") {
		return ErrEncryptedKey
	}

	_, err := os.Stat(privateKeyPath)
	if err != nil {
		if os.IsNotExist(err) {
			err := ioutil.WriteFile(privateKeyPath, []byte(driver.PrivateKey), 0600)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}

	return os.Setenv("GIT_SSH_COMMAND", "ssh -i "+privateKeyPath)
}

func (driver *GitDriver) readVersion() (semver.Version, bool, error) {
	var currentVersionStr string
	versionFile, err := os.Open(filepath.Join(gitRepoDir, driver.File))
	if err != nil {
		if os.IsNotExist(err) {
			return semver.Version{}, false, nil
		}

		return semver.Version{}, false, err
	}

	defer versionFile.Close()

	_, err = fmt.Fscanf(versionFile, "%s", &currentVersionStr)
	if err != nil {
		return semver.Version{}, false, err
	}

	currentVersion, err := semver.Parse(currentVersionStr)
	if err != nil {
		return semver.Version{}, false, err
	}

	return currentVersion, true, nil
}

const nothingToCommitString = "nothing to commit"
const falsePushString = "Everything up-to-date"
const pushRejectedString = "[rejected]"
const pushRemoteRejectedString = "[remote rejected]"

func (driver *GitDriver) writeVersion(newVersion semver.Version) (bool, error) {
	err := ioutil.WriteFile(filepath.Join(gitRepoDir, driver.File), []byte(newVersion.String()+"\n"), 0644)
	if err != nil {
		return false, err
	}

	gitAdd := exec.Command("git", "add", driver.File)
	gitAdd.Dir = gitRepoDir
	gitAdd.Stdout = os.Stderr
	gitAdd.Stderr = os.Stderr
	if err := gitAdd.Run(); err != nil {
		return false, err
	}

	gitCommit := exec.Command("git", "commit", "-m", "bump to "+newVersion.String())
	gitCommit.Dir = gitRepoDir
	gitCommit.Stdout = os.Stderr
	gitCommit.Stderr = os.Stderr

	commitOutput, err := gitCommit.CombinedOutput()

	if strings.Contains(string(commitOutput), nothingToCommitString) {
		return true, nil
	}

	if err != nil {
		os.Stderr.Write(commitOutput)
		return false, err
	}

	gitPush := exec.Command("git", "push", "origin", "HEAD:"+driver.Branch)
	gitPush.Dir = gitRepoDir

	pushOutput, err := gitPush.CombinedOutput()

	if strings.Contains(string(pushOutput), falsePushString) {
		return false, nil
	}

	if strings.Contains(string(pushOutput), pushRejectedString) {
		return false, nil
	}

	if strings.Contains(string(pushOutput), pushRemoteRejectedString) {
		return false, nil
	}

	if err != nil {
		os.Stderr.Write(pushOutput)
		return false, err
	}

	return true, nil
}
<|endoftext|>


<commit_before>package openapi

import (
	"reflect"
	"testing"
)

const (
	exampleCom  = "https://example.com"
	exampleMail = "foo@example.com"
)

type candidate struct {
	label  string
	in     validater
	hasErr bool
}

func testValidater(t *testing.T, candidates []candidate) {
	t.Helper()
	for _, c := range candidates {
		if err := c.in.Validate(); (err != nil) != c.hasErr {
			if c.hasErr {
				t.Error("error should be occurred, but not")
				continue
			}
			t.Errorf("error is occurred: %s", err)
		}
	}
}

func TestHasDuplicatedParameter(t *testing.T) {
	t.Run("no duplicated param", testHasDuplicatedParameterFalse)
	t.Run("there's duplicated param", testHasDuplicatedParameterTrue)
}

func testHasDuplicatedParameterFalse(t *testing.T) {
	params := []*Parameter{
		&Parameter{Name: "foo", In: "header"},
		&Parameter{Name: "foo", In: "path", Required: true},
		&Parameter{Name: "bar", In: "path", Required: true},
	}
	if hasDuplicatedParameter(params) {
		t.Error("should return false")
	}
}

func testHasDuplicatedParameterTrue(t *testing.T) {
	params := []*Parameter{
		&Parameter{Name: "foo", In: "header"},
		&Parameter{Name: "foo", In: "header"},
	}
	if !hasDuplicatedParameter(params) {
		t.Error("should return true")
	}
}

func TestMustURL(t *testing.T) {
	candidates := []struct {
		label  string
		in     string
		hasErr bool
	}{
		{"empty", "", true},
		{"valid HTTP url", "http://example.com", false},
		{"allowed relative path", "foo/bar/baz", true},
		{"absolute path", "/foo/bar/baz", false},
		{"plain string", "foobarbaz", true},
	}
	for _, c := range candidates {
		if err := mustURL(c.label, c.in); (err != nil) != c.hasErr {
			t.Logf("error occured at %s", c.label)
			if c.hasErr {
				t.Error("error should occured, but not")
				return
			}
			t.Error("error should not occurred, but occurred")
			return
		}
	}
}

func TestDocumentValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Document{}, true},
		{"withInvalidVersion", Document{Version: "1.0"}, true},
		{"withVersion", Document{Version: "3.0.0"}, true},
		{"valid", Document{Version: "3.0.0", Info: &Info{Title: "foo", TermsOfService: exampleCom, Version: "1.0"}, Paths: Paths{}}, false},
	}
	testValidater(t, candidates)
}

func TestValidateOASVersion(t *testing.T) {
	candidates := []struct {
		label  string
		in     string
		hasErr bool
	}{
		{"empty", "", true},
		{"invalidVersion", "foobar", true},
		{"swagger", "2.0", true},
		{"valid", "3.0.0", false},
	}
	for _, c := range candidates {
		if err := validateOASVersion(c.in); (err != nil) != c.hasErr {
			t.Log(c.label)
			if c.hasErr {
				t.Error("error should be occurred, but not")
				continue
			}
			t.Errorf("error should not be occured: %s", err)
		}
	}
}

func TestInfoValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Info{}, true},
	}
	testValidater(t, candidates)
}

func TestContactValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Contact{}, true},
		{"withURL", Contact{URL: exampleCom}, false},
		{"invalidURL", Contact{URL: "foobar"}, true},
		{"withEmail", Contact{Email: exampleMail}, true},
		{"valid", Contact{URL: exampleCom, Email: exampleMail}, false},
		{"invalidEmail", Contact{URL: exampleCom, Email: "foobar"}, true},
	}

	testValidater(t, candidates)
}

func TestLicenseValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", License{}, true},
		{"withName", License{Name: "foobar"}, true},
		{"withURL", License{URL: exampleCom}, true},
		{"invalidURL", License{Name: "foobar", URL: "foobar"}, true},
		{"valid", License{Name: "foobar", URL: exampleCom}, false},
	}
	testValidater(t, candidates)
}

func TestServerValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Server{}, true},
		{"invalidURL", Server{URL: "foobar%"}, true},
		{"withURL", Server{URL: exampleCom}, false},
	}
	testValidater(t, candidates)
}

func TestServerVariableValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", ServerVariable{}, true},
		{"withDefault", ServerVariable{Default: "default"}, false},
	}
	testValidater(t, candidates)
}

func TestComponents(t *testing.T) {
	candidates := []candidate{
		{"empty", Components{}, false},
	}
	testValidater(t, candidates)
}

func TestComponentsValidateKeys(t *testing.T) {
	candidates := []struct {
		label  string
		in     Components
		hasErr bool
	}{
		{"empty", Components{}, false},
	}
	for _, c := range candidates {
		if err := c.in.validateKeys(); (err != nil) != c.hasErr {
			t.Log(c.label)
			if c.hasErr {
				t.Error("error should be occurred, but not")
				continue
			}
			t.Errorf("error should not be occurred: %s", err)
		}
	}
}

func TestReduceComponentKeys(t *testing.T) {
	candidates := []struct {
		label    string
		in       Components
		expected []string
	}{
		{"empty", Components{}, []string{}},
	}
	for _, c := range candidates {
		keys := reduceComponentKeys(c.in)
		if !reflect.DeepEqual(keys, c.expected) {
			t.Log(c.label)
			t.Errorf("%+v != %+v", keys, c.expected)
		}
	}
}

func TestReduceComponentObjects(t *testing.T) {
	candidates := []struct {
		label    string
		in       Components
		expected []validater
	}{
		{"empty", Components{}, []validater{}},
	}
	for _, c := range candidates {
		objects := reduceComponentObjects(c.in)
		if !reflect.DeepEqual(objects, c.expected) {
			t.Log(c.label)
			t.Errorf("%+v != %+v", objects, c.expected)
		}
	}
}

func TestPathsValidate(t *testing.T) {
	t.Run("duplicate pathItem", testPathItemDuplicate)
}

func getPaths(id1, id2 string) Paths {
	return Paths{
		"/foo/bar": &PathItem{
			Get:  &Operation{OperationID: id1, Responses: Responses{"200": &Response{Description: "foo"}}},
			Post: &Operation{OperationID: id2, Responses: Responses{"200": &Response{Description: "foo"}}},
		},
	}
}

func testPathItemDuplicate(t *testing.T) {
	candidates := []candidate{
		{"invalid", getPaths("foobar", "foobar"), true},
		{"valid", getPaths("foo", "bar"), false},
	}
	testValidater(t, candidates)
}

func TestExternalDocumentationValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", ExternalDocumentation{}, true},
		{"invalidURL", ExternalDocumentation{URL: "foobar"}, true},
		{"valid", ExternalDocumentation{URL: exampleCom}, false},
	}
	testValidater(t, candidates)
}

func TestTagValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Tag{}, true},
		{"withEmptyExternalDocs", Tag{ExternalDocs: &ExternalDocumentation{}}, true},
		{"withValidExternalDocs", Tag{ExternalDocs: &ExternalDocumentation{URL: exampleCom}}, true},

		{"withName", Tag{Name: "foo"}, false},
	}
	testValidater(t, candidates)
}

func TestSchemaValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Schema{}, false},
	}
	testValidater(t, candidates)
}

func TestDiscriminatorValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Discriminator{}, true},
		{"withPropertyName", Discriminator{PropertyName: "foobar"}, false},
	}
	testValidater(t, candidates)
}

func TestXMLValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", XML{}, true},
		{"invalidURLNamespace", XML{Namespace: "foobar"}, true},
		{"withNamespace", XML{Namespace: exampleCom}, false},
	}
	testValidater(t, candidates)
}

func TestOAuthFlowValidate(t *testing.T) {
	mockScopes := map[string]string{"foo": "bar"}

	empty := OAuthFlow{}
	aURL := OAuthFlow{AuthorizationURL: exampleCom}
	tURL := OAuthFlow{TokenURL: exampleCom}
	rURL := OAuthFlow{RefreshURL: exampleCom}
	scopes := OAuthFlow{Scopes: mockScopes}
	atURL := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom}
	arURL := OAuthFlow{AuthorizationURL: exampleCom, RefreshURL: exampleCom}
	aURLscopes := OAuthFlow{AuthorizationURL: exampleCom, Scopes: mockScopes}
	trURL := OAuthFlow{TokenURL: exampleCom, RefreshURL: exampleCom}
	tURLscopes := OAuthFlow{TokenURL: exampleCom, Scopes: mockScopes}
	rURLscopes := OAuthFlow{RefreshURL: exampleCom, Scopes: mockScopes}
	atrURL := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom, RefreshURL: exampleCom}
	atURLscopes := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom, Scopes: mockScopes}
	arURLscopes := OAuthFlow{AuthorizationURL: exampleCom, RefreshURL: exampleCom, Scopes: mockScopes}
	trURLscopes := OAuthFlow{TokenURL: exampleCom, RefreshURL: exampleCom, Scopes: mockScopes}
	atrURLscopes := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom, RefreshURL: exampleCom, Scopes: mockScopes}
	invalidURL := OAuthFlow{AuthorizationURL: "foobar", TokenURL: "foobar", RefreshURL: "foobar", Scopes: mockScopes}
	zeroMap := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom, RefreshURL: exampleCom, Scopes: map[string]string{}}

	candidates := []struct {
		label   string
		in      OAuthFlow
		haveErr [4]bool
	}{
		{"empty", empty, [4]bool{true, true, true, true}},
		{"aURL", aURL, [4]bool{true, true, true, true}},
		{"tURL", tURL, [4]bool{true, true, true, true}},
		{"rURL", rURL, [4]bool{true, true, true, true}},
		{"scopes", scopes, [4]bool{true, true, true, true}},
		{"aURL/tURL", atURL, [4]bool{true, true, true, true}},
		{"aURL/rURL", arURL, [4]bool{true, true, true, true}},
		{"aURL/scopes", aURLscopes, [4]bool{false, true, true, true}},
		{"tURL/rURL", trURL, [4]bool{true, true, true, true}},
		{"tURL/scopes", tURLscopes, [4]bool{true, false, false, true}},
		{"rURL/scopes", rURLscopes, [4]bool{true, true, true, true}},
		{"aURL/tURL/rURL", atrURL, [4]bool{true, true, true, true}},
		{"aURL/tURL/scopes", atURLscopes, [4]bool{false, false, false, false}},
		{"aURL/rURL/scopes", arURLscopes, [4]bool{false, true, true, true}},
		{"tURL/rURL/scopes", trURLscopes, [4]bool{true, false, false, true}},
		{"aURL/tURL/rURL/scopes", atrURLscopes, [4]bool{false, false, false, false}},

		{"invalidURL", invalidURL, [4]bool{true, true, true, true}},
		{"zero length map", zeroMap, [4]bool{true, true, true, true}},
	}
	for _, c := range candidates {
		testOAuthFlowValidate(t, c.label, c.in, c.haveErr)
	}
}

var flowTypes = []string{"implicit", "password", "clientCredentials", "authorizationCode"}

func testOAuthFlowValidate(t *testing.T, label string, oauthFlow OAuthFlow, haveErr [4]bool) {
	if err := oauthFlow.Validate(""); err == nil {
		t.Logf("%s-empty", label)
		t.Error("error should be occurred, but not")
	}
	if err := oauthFlow.Validate("foobar"); err == nil {
		t.Logf("%s-wrongtype", label)
		t.Error("error should be occurred, but not")
	}
	for i, flowType := range flowTypes {
		if err := oauthFlow.Validate(flowType); (err != nil) != haveErr[i] {
			t.Logf("%s-%s", label, flowType)
			if haveErr[i] {
				t.Error("error should be occurred, but not")
				continue
			}
			t.Error("error should not be occurred, but occurred")
			t.Log(err)
		}
	}
}
<commit_msg>add test for validateAll<commit_after>package openapi

import (
	"errors"
	"reflect"
	"testing"
)

const (
	exampleCom  = "https://example.com"
	exampleMail = "foo@example.com"
)

type candidate struct {
	label  string
	in     validater
	hasErr bool
}

func testValidater(t *testing.T, candidates []candidate) {
	t.Helper()
	for _, c := range candidates {
		if err := c.in.Validate(); (err != nil) != c.hasErr {
			if c.hasErr {
				t.Error("error should be occurred, but not")
				continue
			}
			t.Errorf("error is occurred: %s", err)
		}
	}
}

type mockValidater struct {
	err error
}

func (v mockValidater) Validate() error {
	return v.err
}

func TestValidateAll(t *testing.T) {
	valid := mockValidater{}
	invalid := mockValidater{errors.New("err")}

	candidates := []struct {
		label  string
		in     []validater
		hasErr bool
	}{
		{"nil", nil, false},
		{"empty", []validater{}, false},
		{"all valid", []validater{valid, valid, valid}, false},
		{"have invalid", []validater{valid, invalid, valid}, true},
		{"have nil", []validater{valid, nil, valid, valid}, false},
	}
	for _, c := range candidates {
		if err := validateAll(c.in); (err != nil) != c.hasErr {
			t.Log(c.label)
			t.Errorf("error: %s", err)
		}
	}
}

func TestHasDuplicatedParameter(t *testing.T) {
	t.Run("no duplicated param", testHasDuplicatedParameterFalse)
	t.Run("there's duplicated param", testHasDuplicatedParameterTrue)
}

func testHasDuplicatedParameterFalse(t *testing.T) {
	params := []*Parameter{
		&Parameter{Name: "foo", In: "header"},
		&Parameter{Name: "foo", In: "path", Required: true},
		&Parameter{Name: "bar", In: "path", Required: true},
	}
	if hasDuplicatedParameter(params) {
		t.Error("should return false")
	}
}

func testHasDuplicatedParameterTrue(t *testing.T) {
	params := []*Parameter{
		&Parameter{Name: "foo", In: "header"},
		&Parameter{Name: "foo", In: "header"},
	}
	if !hasDuplicatedParameter(params) {
		t.Error("should return true")
	}
}

func TestMustURL(t *testing.T) {
	candidates := []struct {
		label  string
		in     string
		hasErr bool
	}{
		{"empty", "", true},
		{"valid HTTP url", "http://example.com", false},
		{"allowed relative path", "foo/bar/baz", true},
		{"absolute path", "/foo/bar/baz", false},
		{"plain string", "foobarbaz", true},
	}
	for _, c := range candidates {
		if err := mustURL(c.label, c.in); (err != nil) != c.hasErr {
			t.Logf("error occured at %s", c.label)
			if c.hasErr {
				t.Error("error should occured, but not")
				return
			}
			t.Error("error should not occurred, but occurred")
			return
		}
	}
}

func TestDocumentValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Document{}, true},
		{"withInvalidVersion", Document{Version: "1.0"}, true},
		{"withVersion", Document{Version: "3.0.0"}, true},
		{"valid", Document{Version: "3.0.0", Info: &Info{Title: "foo", TermsOfService: exampleCom, Version: "1.0"}, Paths: Paths{}}, false},
	}
	testValidater(t, candidates)
}

func TestValidateOASVersion(t *testing.T) {
	candidates := []struct {
		label  string
		in     string
		hasErr bool
	}{
		{"empty", "", true},
		{"invalidVersion", "foobar", true},
		{"swagger", "2.0", true},
		{"valid", "3.0.0", false},
	}
	for _, c := range candidates {
		if err := validateOASVersion(c.in); (err != nil) != c.hasErr {
			t.Log(c.label)
			if c.hasErr {
				t.Error("error should be occurred, but not")
				continue
			}
			t.Errorf("error should not be occured: %s", err)
		}
	}
}

func TestInfoValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Info{}, true},
	}
	testValidater(t, candidates)
}

func TestContactValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Contact{}, true},
		{"withURL", Contact{URL: exampleCom}, false},
		{"invalidURL", Contact{URL: "foobar"}, true},
		{"withEmail", Contact{Email: exampleMail}, true},
		{"valid", Contact{URL: exampleCom, Email: exampleMail}, false},
		{"invalidEmail", Contact{URL: exampleCom, Email: "foobar"}, true},
	}

	testValidater(t, candidates)
}

func TestLicenseValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", License{}, true},
		{"withName", License{Name: "foobar"}, true},
		{"withURL", License{URL: exampleCom}, true},
		{"invalidURL", License{Name: "foobar", URL: "foobar"}, true},
		{"valid", License{Name: "foobar", URL: exampleCom}, false},
	}
	testValidater(t, candidates)
}

func TestServerValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Server{}, true},
		{"invalidURL", Server{URL: "foobar%"}, true},
		{"withURL", Server{URL: exampleCom}, false},
	}
	testValidater(t, candidates)
}

func TestServerVariableValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", ServerVariable{}, true},
		{"withDefault", ServerVariable{Default: "default"}, false},
	}
	testValidater(t, candidates)
}

func TestComponents(t *testing.T) {
	candidates := []candidate{
		{"empty", Components{}, false},
	}
	testValidater(t, candidates)
}

func TestComponentsValidateKeys(t *testing.T) {
	candidates := []struct {
		label  string
		in     Components
		hasErr bool
	}{
		{"empty", Components{}, false},
	}
	for _, c := range candidates {
		if err := c.in.validateKeys(); (err != nil) != c.hasErr {
			t.Log(c.label)
			if c.hasErr {
				t.Error("error should be occurred, but not")
				continue
			}
			t.Errorf("error should not be occurred: %s", err)
		}
	}
}

func TestReduceComponentKeys(t *testing.T) {
	candidates := []struct {
		label    string
		in       Components
		expected []string
	}{
		{"empty", Components{}, []string{}},
	}
	for _, c := range candidates {
		keys := reduceComponentKeys(c.in)
		if !reflect.DeepEqual(keys, c.expected) {
			t.Log(c.label)
			t.Errorf("%+v != %+v", keys, c.expected)
		}
	}
}

func TestReduceComponentObjects(t *testing.T) {
	candidates := []struct {
		label    string
		in       Components
		expected []validater
	}{
		{"empty", Components{}, []validater{}},
	}
	for _, c := range candidates {
		objects := reduceComponentObjects(c.in)
		if !reflect.DeepEqual(objects, c.expected) {
			t.Log(c.label)
			t.Errorf("%+v != %+v", objects, c.expected)
		}
	}
}

func TestPathsValidate(t *testing.T) {
	t.Run("duplicate pathItem", testPathItemDuplicate)
}

func getPaths(id1, id2 string) Paths {
	return Paths{
		"/foo/bar": &PathItem{
			Get:  &Operation{OperationID: id1, Responses: Responses{"200": &Response{Description: "foo"}}},
			Post: &Operation{OperationID: id2, Responses: Responses{"200": &Response{Description: "foo"}}},
		},
	}
}

func testPathItemDuplicate(t *testing.T) {
	candidates := []candidate{
		{"invalid", getPaths("foobar", "foobar"), true},
		{"valid", getPaths("foo", "bar"), false},
	}
	testValidater(t, candidates)
}

func TestExternalDocumentationValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", ExternalDocumentation{}, true},
		{"invalidURL", ExternalDocumentation{URL: "foobar"}, true},
		{"valid", ExternalDocumentation{URL: exampleCom}, false},
	}
	testValidater(t, candidates)
}

func TestTagValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Tag{}, true},
		{"withEmptyExternalDocs", Tag{ExternalDocs: &ExternalDocumentation{}}, true},
		{"withValidExternalDocs", Tag{ExternalDocs: &ExternalDocumentation{URL: exampleCom}}, true},

		{"withName", Tag{Name: "foo"}, false},
	}
	testValidater(t, candidates)
}

func TestSchemaValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Schema{}, false},
	}
	testValidater(t, candidates)
}

func TestDiscriminatorValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", Discriminator{}, true},
		{"withPropertyName", Discriminator{PropertyName: "foobar"}, false},
	}
	testValidater(t, candidates)
}

func TestXMLValidate(t *testing.T) {
	candidates := []candidate{
		{"empty", XML{}, true},
		{"invalidURLNamespace", XML{Namespace: "foobar"}, true},
		{"withNamespace", XML{Namespace: exampleCom}, false},
	}
	testValidater(t, candidates)
}

func TestOAuthFlowValidate(t *testing.T) {
	mockScopes := map[string]string{"foo": "bar"}

	empty := OAuthFlow{}
	aURL := OAuthFlow{AuthorizationURL: exampleCom}
	tURL := OAuthFlow{TokenURL: exampleCom}
	rURL := OAuthFlow{RefreshURL: exampleCom}
	scopes := OAuthFlow{Scopes: mockScopes}
	atURL := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom}
	arURL := OAuthFlow{AuthorizationURL: exampleCom, RefreshURL: exampleCom}
	aURLscopes := OAuthFlow{AuthorizationURL: exampleCom, Scopes: mockScopes}
	trURL := OAuthFlow{TokenURL: exampleCom, RefreshURL: exampleCom}
	tURLscopes := OAuthFlow{TokenURL: exampleCom, Scopes: mockScopes}
	rURLscopes := OAuthFlow{RefreshURL: exampleCom, Scopes: mockScopes}
	atrURL := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom, RefreshURL: exampleCom}
	atURLscopes := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom, Scopes: mockScopes}
	arURLscopes := OAuthFlow{AuthorizationURL: exampleCom, RefreshURL: exampleCom, Scopes: mockScopes}
	trURLscopes := OAuthFlow{TokenURL: exampleCom, RefreshURL: exampleCom, Scopes: mockScopes}
	atrURLscopes := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom, RefreshURL: exampleCom, Scopes: mockScopes}
	invalidURL := OAuthFlow{AuthorizationURL: "foobar", TokenURL: "foobar", RefreshURL: "foobar", Scopes: mockScopes}
	zeroMap := OAuthFlow{AuthorizationURL: exampleCom, TokenURL: exampleCom, RefreshURL: exampleCom, Scopes: map[string]string{}}

	candidates := []struct {
		label   string
		in      OAuthFlow
		haveErr [4]bool
	}{
		{"empty", empty, [4]bool{true, true, true, true}},
		{"aURL", aURL, [4]bool{true, true, true, true}},
		{"tURL", tURL, [4]bool{true, true, true, true}},
		{"rURL", rURL, [4]bool{true, true, true, true}},
		{"scopes", scopes, [4]bool{true, true, true, true}},
		{"aURL/tURL", atURL, [4]bool{true, true, true, true}},
		{"aURL/rURL", arURL, [4]bool{true, true, true, true}},
		{"aURL/scopes", aURLscopes, [4]bool{false, true, true, true}},
		{"tURL/rURL", trURL, [4]bool{true, true, true, true}},
		{"tURL/scopes", tURLscopes, [4]bool{true, false, false, true}},
		{"rURL/scopes", rURLscopes, [4]bool{true, true, true, true}},
		{"aURL/tURL/rURL", atrURL, [4]bool{true, true, true, true}},
		{"aURL/tURL/scopes", atURLscopes, [4]bool{false, false, false, false}},
		{"aURL/rURL/scopes", arURLscopes, [4]bool{false, true, true, true}},
		{"tURL/rURL/scopes", trURLscopes, [4]bool{true, false, false, true}},
		{"aURL/tURL/rURL/scopes", atrURLscopes, [4]bool{false, false, false, false}},

		{"invalidURL", invalidURL, [4]bool{true, true, true, true}},
		{"zero length map", zeroMap, [4]bool{true, true, true, true}},
	}
	for _, c := range candidates {
		testOAuthFlowValidate(t, c.label, c.in, c.haveErr)
	}
}

var flowTypes = []string{"implicit", "password", "clientCredentials", "authorizationCode"}

func testOAuthFlowValidate(t *testing.T, label string, oauthFlow OAuthFlow, haveErr [4]bool) {
	if err := oauthFlow.Validate(""); err == nil {
		t.Logf("%s-empty", label)
		t.Error("error should be occurred, but not")
	}
	if err := oauthFlow.Validate("foobar"); err == nil {
		t.Logf("%s-wrongtype", label)
		t.Error("error should be occurred, but not")
	}
	for i, flowType := range flowTypes {
		if err := oauthFlow.Validate(flowType); (err != nil) != haveErr[i] {
			t.Logf("%s-%s", label, flowType)
			if haveErr[i] {
				t.Error("error should be occurred, but not")
				continue
			}
			t.Error("error should not be occurred, but occurred")
			t.Log(err)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package validation

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/api"
	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/conn"
	"github.com/getgauge/gauge/gauge"
	"github.com/getgauge/gauge/gauge_messages"
	"github.com/getgauge/gauge/logger"
	"github.com/getgauge/gauge/manifest"
	"github.com/getgauge/gauge/parser"
	"github.com/getgauge/gauge/runner"
	"github.com/golang/protobuf/proto"
)

var TableRows = ""

type ValidationErrMaps struct {
	SpecErrs     map[*gauge.Specification][]error
	ScenarioErrs map[*gauge.Scenario][]error
	StepErrs     map[*gauge.Step]error
}

type validator struct {
	manifest           *manifest.Manifest
	specsToExecute     []*gauge.Specification
	runner             runner.Runner
	conceptsDictionary *gauge.ConceptDictionary
}

type specValidator struct {
	specification       *gauge.Specification
	runner              runner.Runner
	conceptsDictionary  *gauge.ConceptDictionary
	validationErrors    []error
	stepValidationCache map[string]error
}

type StepValidationError struct {
	step      *gauge.Step
	message   string
	fileName  string
	errorType *gauge_messages.StepValidateResponse_ErrorType
}

type SpecValidationError struct {
	message  string
	fileName string
}

func NewValidationErrMaps() *ValidationErrMaps {
	return &ValidationErrMaps{
		SpecErrs:     make(map[*gauge.Specification][]error),
		ScenarioErrs: make(map[*gauge.Scenario][]error),
		StepErrs:     make(map[*gauge.Step]error),
	}
}

func (s StepValidationError) Error() string {
	return fmt.Sprintf("%s:%d: %s => '%s'", s.fileName, s.step.LineNo, s.message, s.step.GetLineText())
}

func (s SpecValidationError) Error() string {
	return fmt.Sprintf("%s: %s", s.fileName, s.message)
}

func NewSpecValidationError(m string, f string) *SpecValidationError {
	return &SpecValidationError{message: m, fileName: f}
}

func NewStepValidationError(s *gauge.Step, m string, f string, e *gauge_messages.StepValidateResponse_ErrorType) *StepValidationError {
	return &StepValidationError{step: s, message: m, fileName: f, errorType: e}
}

func Validate(args []string) {
	if len(args) == 0 {
		args = append(args, common.SpecsDirectoryName)
	}
	res := ValidateSpecs(args, false)
	if len(res.Errs) > 0 {
		os.Exit(1)
	}
	if res.SpecCollection.Size() < 1 {
		logger.Info("No specifications found in %s.", strings.Join(args, ", "))
		res.Runner.Kill()
		os.Exit(0)
	}
	res.Runner.Kill()
	if len(res.ErrMap.StepErrs) > 0 {
		os.Exit(1)
	}
	logger.Info("No error found.")
}

//TODO : duplicate in execute.go. Need to fix runner init.
func startAPI(debug bool) runner.Runner {
	sc := api.StartAPI(debug)
	select {
	case runner := <-sc.RunnerChan:
		return runner
	case err := <-sc.ErrorChan:
		logger.Fatalf("Failed to start gauge API: %s", err.Error())
	}
	return nil
}

type ValidationResult struct {
	SpecCollection *gauge.SpecCollection
	ErrMap         *ValidationErrMaps
	Runner         runner.Runner
	Errs           []error
}

func NewValidationResult(s *gauge.SpecCollection, errMap *ValidationErrMaps, r runner.Runner, e ...error) *ValidationResult {
	return &ValidationResult{SpecCollection: s, ErrMap: errMap, Runner: r, Errs: e}
}

func ValidateSpecs(args []string, debug bool) *ValidationResult {
	manifest, err := manifest.ProjectManifest()
	if err != nil {
		logger.Errorf(err.Error())
		return NewValidationResult(nil, nil, nil, err)
	}
	conceptDict, res := parser.ParseConcepts()
	if len(res.CriticalErrors) > 0 {
		var errs []error
		for _, err := range res.CriticalErrors {
			errs = append(errs, err)
		}
		return NewValidationResult(nil, nil, nil, errs...)
	}
	s, specsFailed := parser.ParseSpecs(args, conceptDict)
	r := startAPI(debug)
	vErrs := newValidator(manifest, s, r, conceptDict).validate()
	errMap := getErrMap(vErrs)
	printValidationFailures(vErrs)
	if specsFailed || !res.Ok {
		r.Kill()
		return NewValidationResult(nil, nil, nil, errors.New("Parsing failed."))
	}
	return NewValidationResult(gauge.NewSpecCollection(s), errMap, r)
}

func getErrMap(validationErrors validationErrors) *ValidationErrMaps {
	errMap := NewValidationErrMaps()
	for spec, valErrors := range validationErrors {
		for _, err := range valErrors {
			switch err.(type) {
			case *StepValidationError:
				errMap.StepErrs[err.(*StepValidationError).step] = err
			case *SpecValidationError:
				errMap.SpecErrs[spec] = append(errMap.SpecErrs[spec], err.(*SpecValidationError))
			}
		}
		skippedScnInSpec := 0
		for _, scenario := range spec.Scenarios {
			fillScenarioErrors(scenario, errMap, scenario.Steps)
			if _, ok := errMap.ScenarioErrs[scenario]; ok {
				skippedScnInSpec++
			}
		}
		if len(spec.Scenarios) > 0 && skippedScnInSpec == len(spec.Scenarios) {
			errMap.SpecErrs[spec] = append(errMap.SpecErrs[spec], errMap.ScenarioErrs[spec.Scenarios[0]]...)
		}
		fillSpecErrors(spec, errMap, append(spec.Contexts, spec.TearDownSteps...))
	}
	return errMap
}

func fillScenarioErrors(scenario *gauge.Scenario, errMap *ValidationErrMaps, steps []*gauge.Step) {
	for _, step := range steps {
		if step.IsConcept {
			fillScenarioErrors(scenario, errMap, step.ConceptSteps)
		}
		if err, ok := errMap.StepErrs[step]; ok {
			errMap.ScenarioErrs[scenario] = append(errMap.ScenarioErrs[scenario], err)
		}
	}
}

func fillSpecErrors(spec *gauge.Specification, errMap *ValidationErrMaps, steps []*gauge.Step) {
	for _, context := range steps {
		if context.IsConcept {
			fillSpecErrors(spec, errMap, context.ConceptSteps)
		}
		if err, ok := errMap.StepErrs[context]; ok {
			errMap.SpecErrs[spec] = append(errMap.SpecErrs[spec], err)
			for _, scenario := range spec.Scenarios {
				if _, ok := errMap.ScenarioErrs[scenario]; !ok {
					errMap.ScenarioErrs[scenario] = append(errMap.ScenarioErrs[scenario], err)
				}
			}
		}
	}
}

func printValidationFailures(validationErrors validationErrors) {
	for _, errs := range validationErrors {
		for _, e := range errs {
			logger.Errorf("[ValidationError] %s", e.Error())
		}
	}
}

type validationErrors map[*gauge.Specification][]error

func newValidator(m *manifest.Manifest, s []*gauge.Specification, r runner.Runner, c *gauge.ConceptDictionary) *validator {
	return &validator{manifest: m, specsToExecute: s, runner: r, conceptsDictionary: c}
}

func (v *validator) validate() validationErrors {
	validationStatus := make(validationErrors)
	specValidator := &specValidator{runner: v.runner, conceptsDictionary: v.conceptsDictionary, stepValidationCache: make(map[string]error)}
	for _, spec := range v.specsToExecute {
		specValidator.specification = spec
		validationErrors := specValidator.validate()
		if len(validationErrors) != 0 {
			validationStatus[spec] = validationErrors
		}
	}
	if len(validationStatus) > 0 {
		return validationStatus
	}
	return nil
}

func (v *specValidator) validate() []error {
	v.specification.Traverse(v)
	return v.validationErrors
}

func (v *specValidator) Step(s *gauge.Step) {
	if s.IsConcept {
		for _, c := range s.ConceptSteps {
			v.Step(c)
		}
		return
	}
	val, ok := v.stepValidationCache[s.Value]
	if !ok {
		err := v.validateStep(s)
		if err != nil {
			v.validationErrors = append(v.validationErrors, err)
		}
		v.stepValidationCache[s.Value] = err
		return
	}
	if val != nil {
		valErr := val.(*StepValidationError)
		if s.Parent == nil {
			v.validationErrors = append(v.validationErrors,
				NewStepValidationError(s, valErr.message, v.specification.FileName, valErr.errorType))
		} else {
			cpt := v.conceptsDictionary.Search(s.Parent.Value)
			v.validationErrors = append(v.validationErrors,
				NewStepValidationError(s, valErr.message, cpt.FileName, valErr.errorType))
		}
	}
}

var invalidResponse gauge_messages.StepValidateResponse_ErrorType = -1

var getResponseFromRunner = func(m *gauge_messages.Message, v *specValidator) (*gauge_messages.Message, error) {
	return conn.GetResponseForMessageWithTimeout(m, v.runner.Connection(), config.RunnerRequestTimeout())
}

func (v *specValidator) validateStep(s *gauge.Step) error {
	m := &gauge_messages.Message{MessageType: gauge_messages.Message_StepValidateRequest.Enum(),
		StepValidateRequest: &gauge_messages.StepValidateRequest{StepText: proto.String(s.Value), NumberOfParameters: proto.Int(len(s.Args))}}
	r, err := getResponseFromRunner(m, v)
	if err != nil {
		return NewStepValidationError(s, err.Error(), v.specification.FileName, nil)
	}
	if r.GetMessageType() == gauge_messages.Message_StepValidateResponse {
		res := r.GetStepValidateResponse()
		if !res.GetIsValid() {
			msg := getMessage(res.GetErrorType().String())
			if s.Parent == nil {
				return NewStepValidationError(s, msg, v.specification.FileName, res.ErrorType)
			}
			cpt := v.conceptsDictionary.Search(s.Parent.Value)
			return NewStepValidationError(s, msg, cpt.FileName, res.ErrorType)
		}
		return nil
	}
	return NewStepValidationError(s, "Invalid response from runner for Validation request", v.specification.FileName, &invalidResponse)
}

func getMessage(message string) string {
	lower := strings.ToLower(strings.Replace(message, "_", " ", -1))
	return strings.ToUpper(lower[:1]) + lower[1:]
}

func (v *specValidator) ContextStep(step *gauge.Step) {
	v.Step(step)
}

func (v *specValidator) TearDown(step *gauge.TearDown) {
}

func (v *specValidator) SpecHeading(heading *gauge.Heading) {
}

func (v *specValidator) SpecTags(tags *gauge.Tags) {
}

func (v *specValidator) ScenarioTags(tags *gauge.Tags) {

}

func (v *specValidator) DataTable(dataTable *gauge.Table) {

}

func (v *specValidator) Scenario(scenario *gauge.Scenario) {

}

func (v *specValidator) ScenarioHeading(heading *gauge.Heading) {
}

func (v *specValidator) Comment(comment *gauge.Comment) {
}

func (v *specValidator) ExternalDataTable(dataTable *gauge.DataTable) {

}

func (v *specValidator) Specification(specification *gauge.Specification) {
	v.validationErrors = make([]error, 0)
	err := validateDataTableRange(specification.DataTable.Table.GetRowCount())
	if err != nil {
		v.validationErrors = append(v.validationErrors, NewSpecValidationError(err.Error(), specification.FileName))
	}
}

func validateDataTableRange(rowCount int) error {
	if TableRows == "" {
		return nil
	}
	if strings.Contains(TableRows, "-") {
		indexes := strings.Split(TableRows, "-")
		if len(indexes) > 2 {
			return fmt.Errorf("Table rows range '%s' is invalid => Table rows range should be of format rowNumber-rowNumber", TableRows)
		}
		if err := validateTableRow(indexes[0], rowCount); err != nil {
			return err
		}
		if err := validateTableRow(indexes[1], rowCount); err != nil {
			return err
		}
	} else {
		indexes := strings.Split(TableRows, ",")
		for _, i := range indexes {
			if err := validateTableRow(i, rowCount); err != nil {
				return err
			}
		}
	}
	return nil
}

func validateTableRow(rowNumber string, rowCount int) error {
	if rowNumber = strings.TrimSpace(rowNumber); rowNumber == "" {
		return fmt.Errorf("Table rows range validation failed => Row number cannot be empty")
	}
	row, err := strconv.Atoi(rowNumber)
	if err != nil {
		return fmt.Errorf("Table rows range validation failed => Failed to parse '%s' to row number", rowNumber)
	}
	if row < 1 || row > rowCount {
		return fmt.Errorf("Table rows range validation failed => Table row number '%d' is out of range", row)
	}
	return nil
}
<commit_msg>Removing colon in validation error format #533<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package validation

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/api"
	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/conn"
	"github.com/getgauge/gauge/gauge"
	"github.com/getgauge/gauge/gauge_messages"
	"github.com/getgauge/gauge/logger"
	"github.com/getgauge/gauge/manifest"
	"github.com/getgauge/gauge/parser"
	"github.com/getgauge/gauge/runner"
	"github.com/golang/protobuf/proto"
)

var TableRows = ""

type ValidationErrMaps struct {
	SpecErrs     map[*gauge.Specification][]error
	ScenarioErrs map[*gauge.Scenario][]error
	StepErrs     map[*gauge.Step]error
}

type validator struct {
	manifest           *manifest.Manifest
	specsToExecute     []*gauge.Specification
	runner             runner.Runner
	conceptsDictionary *gauge.ConceptDictionary
}

type specValidator struct {
	specification       *gauge.Specification
	runner              runner.Runner
	conceptsDictionary  *gauge.ConceptDictionary
	validationErrors    []error
	stepValidationCache map[string]error
}

type StepValidationError struct {
	step      *gauge.Step
	message   string
	fileName  string
	errorType *gauge_messages.StepValidateResponse_ErrorType
}

type SpecValidationError struct {
	message  string
	fileName string
}

func NewValidationErrMaps() *ValidationErrMaps {
	return &ValidationErrMaps{
		SpecErrs:     make(map[*gauge.Specification][]error),
		ScenarioErrs: make(map[*gauge.Scenario][]error),
		StepErrs:     make(map[*gauge.Step]error),
	}
}

func (s StepValidationError) Error() string {
	return fmt.Sprintf("%s:%d %s => '%s'", s.fileName, s.step.LineNo, s.message, s.step.GetLineText())
}

func (s SpecValidationError) Error() string {
	return fmt.Sprintf("%s %s", s.fileName, s.message)
}

func NewSpecValidationError(m string, f string) *SpecValidationError {
	return &SpecValidationError{message: m, fileName: f}
}

func NewStepValidationError(s *gauge.Step, m string, f string, e *gauge_messages.StepValidateResponse_ErrorType) *StepValidationError {
	return &StepValidationError{step: s, message: m, fileName: f, errorType: e}
}

func Validate(args []string) {
	if len(args) == 0 {
		args = append(args, common.SpecsDirectoryName)
	}
	res := ValidateSpecs(args, false)
	if len(res.Errs) > 0 {
		os.Exit(1)
	}
	if res.SpecCollection.Size() < 1 {
		logger.Info("No specifications found in %s.", strings.Join(args, ", "))
		res.Runner.Kill()
		os.Exit(0)
	}
	res.Runner.Kill()
	if len(res.ErrMap.StepErrs) > 0 {
		os.Exit(1)
	}
	logger.Info("No error found.")
}

//TODO : duplicate in execute.go. Need to fix runner init.
func startAPI(debug bool) runner.Runner {
	sc := api.StartAPI(debug)
	select {
	case runner := <-sc.RunnerChan:
		return runner
	case err := <-sc.ErrorChan:
		logger.Fatalf("Failed to start gauge API: %s", err.Error())
	}
	return nil
}

type ValidationResult struct {
	SpecCollection *gauge.SpecCollection
	ErrMap         *ValidationErrMaps
	Runner         runner.Runner
	Errs           []error
}

func NewValidationResult(s *gauge.SpecCollection, errMap *ValidationErrMaps, r runner.Runner, e ...error) *ValidationResult {
	return &ValidationResult{SpecCollection: s, ErrMap: errMap, Runner: r, Errs: e}
}

func ValidateSpecs(args []string, debug bool) *ValidationResult {
	manifest, err := manifest.ProjectManifest()
	if err != nil {
		logger.Errorf(err.Error())
		return NewValidationResult(nil, nil, nil, err)
	}
	conceptDict, res := parser.ParseConcepts()
	if len(res.CriticalErrors) > 0 {
		var errs []error
		for _, err := range res.CriticalErrors {
			errs = append(errs, err)
		}
		return NewValidationResult(nil, nil, nil, errs...)
	}
	s, specsFailed := parser.ParseSpecs(args, conceptDict)
	r := startAPI(debug)
	vErrs := newValidator(manifest, s, r, conceptDict).validate()
	errMap := getErrMap(vErrs)
	printValidationFailures(vErrs)
	if specsFailed || !res.Ok {
		r.Kill()
		return NewValidationResult(nil, nil, nil, errors.New("Parsing failed."))
	}
	return NewValidationResult(gauge.NewSpecCollection(s), errMap, r)
}

func getErrMap(validationErrors validationErrors) *ValidationErrMaps {
	errMap := NewValidationErrMaps()
	for spec, valErrors := range validationErrors {
		for _, err := range valErrors {
			switch err.(type) {
			case *StepValidationError:
				errMap.StepErrs[err.(*StepValidationError).step] = err
			case *SpecValidationError:
				errMap.SpecErrs[spec] = append(errMap.SpecErrs[spec], err.(*SpecValidationError))
			}
		}
		skippedScnInSpec := 0
		for _, scenario := range spec.Scenarios {
			fillScenarioErrors(scenario, errMap, scenario.Steps)
			if _, ok := errMap.ScenarioErrs[scenario]; ok {
				skippedScnInSpec++
			}
		}
		if len(spec.Scenarios) > 0 && skippedScnInSpec == len(spec.Scenarios) {
			errMap.SpecErrs[spec] = append(errMap.SpecErrs[spec], errMap.ScenarioErrs[spec.Scenarios[0]]...)
		}
		fillSpecErrors(spec, errMap, append(spec.Contexts, spec.TearDownSteps...))
	}
	return errMap
}

func fillScenarioErrors(scenario *gauge.Scenario, errMap *ValidationErrMaps, steps []*gauge.Step) {
	for _, step := range steps {
		if step.IsConcept {
			fillScenarioErrors(scenario, errMap, step.ConceptSteps)
		}
		if err, ok := errMap.StepErrs[step]; ok {
			errMap.ScenarioErrs[scenario] = append(errMap.ScenarioErrs[scenario], err)
		}
	}
}

func fillSpecErrors(spec *gauge.Specification, errMap *ValidationErrMaps, steps []*gauge.Step) {
	for _, context := range steps {
		if context.IsConcept {
			fillSpecErrors(spec, errMap, context.ConceptSteps)
		}
		if err, ok := errMap.StepErrs[context]; ok {
			errMap.SpecErrs[spec] = append(errMap.SpecErrs[spec], err)
			for _, scenario := range spec.Scenarios {
				if _, ok := errMap.ScenarioErrs[scenario]; !ok {
					errMap.ScenarioErrs[scenario] = append(errMap.ScenarioErrs[scenario], err)
				}
			}
		}
	}
}

func printValidationFailures(validationErrors validationErrors) {
	for _, errs := range validationErrors {
		for _, e := range errs {
			logger.Errorf("[ValidationError] %s", e.Error())
		}
	}
}

type validationErrors map[*gauge.Specification][]error

func newValidator(m *manifest.Manifest, s []*gauge.Specification, r runner.Runner, c *gauge.ConceptDictionary) *validator {
	return &validator{manifest: m, specsToExecute: s, runner: r, conceptsDictionary: c}
}

func (v *validator) validate() validationErrors {
	validationStatus := make(validationErrors)
	specValidator := &specValidator{runner: v.runner, conceptsDictionary: v.conceptsDictionary, stepValidationCache: make(map[string]error)}
	for _, spec := range v.specsToExecute {
		specValidator.specification = spec
		validationErrors := specValidator.validate()
		if len(validationErrors) != 0 {
			validationStatus[spec] = validationErrors
		}
	}
	if len(validationStatus) > 0 {
		return validationStatus
	}
	return nil
}

func (v *specValidator) validate() []error {
	v.specification.Traverse(v)
	return v.validationErrors
}

func (v *specValidator) Step(s *gauge.Step) {
	if s.IsConcept {
		for _, c := range s.ConceptSteps {
			v.Step(c)
		}
		return
	}
	val, ok := v.stepValidationCache[s.Value]
	if !ok {
		err := v.validateStep(s)
		if err != nil {
			v.validationErrors = append(v.validationErrors, err)
		}
		v.stepValidationCache[s.Value] = err
		return
	}
	if val != nil {
		valErr := val.(*StepValidationError)
		if s.Parent == nil {
			v.validationErrors = append(v.validationErrors,
				NewStepValidationError(s, valErr.message, v.specification.FileName, valErr.errorType))
		} else {
			cpt := v.conceptsDictionary.Search(s.Parent.Value)
			v.validationErrors = append(v.validationErrors,
				NewStepValidationError(s, valErr.message, cpt.FileName, valErr.errorType))
		}
	}
}

var invalidResponse gauge_messages.StepValidateResponse_ErrorType = -1

var getResponseFromRunner = func(m *gauge_messages.Message, v *specValidator) (*gauge_messages.Message, error) {
	return conn.GetResponseForMessageWithTimeout(m, v.runner.Connection(), config.RunnerRequestTimeout())
}

func (v *specValidator) validateStep(s *gauge.Step) error {
	m := &gauge_messages.Message{MessageType: gauge_messages.Message_StepValidateRequest.Enum(),
		StepValidateRequest: &gauge_messages.StepValidateRequest{StepText: proto.String(s.Value), NumberOfParameters: proto.Int(len(s.Args))}}
	r, err := getResponseFromRunner(m, v)
	if err != nil {
		return NewStepValidationError(s, err.Error(), v.specification.FileName, nil)
	}
	if r.GetMessageType() == gauge_messages.Message_StepValidateResponse {
		res := r.GetStepValidateResponse()
		if !res.GetIsValid() {
			msg := getMessage(res.GetErrorType().String())
			if s.Parent == nil {
				return NewStepValidationError(s, msg, v.specification.FileName, res.ErrorType)
			}
			cpt := v.conceptsDictionary.Search(s.Parent.Value)
			return NewStepValidationError(s, msg, cpt.FileName, res.ErrorType)
		}
		return nil
	}
	return NewStepValidationError(s, "Invalid response from runner for Validation request", v.specification.FileName, &invalidResponse)
}

func getMessage(message string) string {
	lower := strings.ToLower(strings.Replace(message, "_", " ", -1))
	return strings.ToUpper(lower[:1]) + lower[1:]
}

func (v *specValidator) ContextStep(step *gauge.Step) {
	v.Step(step)
}

func (v *specValidator) TearDown(step *gauge.TearDown) {
}

func (v *specValidator) SpecHeading(heading *gauge.Heading) {
}

func (v *specValidator) SpecTags(tags *gauge.Tags) {
}

func (v *specValidator) ScenarioTags(tags *gauge.Tags) {

}

func (v *specValidator) DataTable(dataTable *gauge.Table) {

}

func (v *specValidator) Scenario(scenario *gauge.Scenario) {

}

func (v *specValidator) ScenarioHeading(heading *gauge.Heading) {
}

func (v *specValidator) Comment(comment *gauge.Comment) {
}

func (v *specValidator) ExternalDataTable(dataTable *gauge.DataTable) {

}

func (v *specValidator) Specification(specification *gauge.Specification) {
	v.validationErrors = make([]error, 0)
	err := validateDataTableRange(specification.DataTable.Table.GetRowCount())
	if err != nil {
		v.validationErrors = append(v.validationErrors, NewSpecValidationError(err.Error(), specification.FileName))
	}
}

func validateDataTableRange(rowCount int) error {
	if TableRows == "" {
		return nil
	}
	if strings.Contains(TableRows, "-") {
		indexes := strings.Split(TableRows, "-")
		if len(indexes) > 2 {
			return fmt.Errorf("Table rows range '%s' is invalid => Table rows range should be of format rowNumber-rowNumber", TableRows)
		}
		if err := validateTableRow(indexes[0], rowCount); err != nil {
			return err
		}
		if err := validateTableRow(indexes[1], rowCount); err != nil {
			return err
		}
	} else {
		indexes := strings.Split(TableRows, ",")
		for _, i := range indexes {
			if err := validateTableRow(i, rowCount); err != nil {
				return err
			}
		}
	}
	return nil
}

func validateTableRow(rowNumber string, rowCount int) error {
	if rowNumber = strings.TrimSpace(rowNumber); rowNumber == "" {
		return fmt.Errorf("Table rows range validation failed => Row number cannot be empty")
	}
	row, err := strconv.Atoi(rowNumber)
	if err != nil {
		return fmt.Errorf("Table rows range validation failed => Failed to parse '%s' to row number", rowNumber)
	}
	if row < 1 || row > rowCount {
		return fmt.Errorf("Table rows range validation failed => Table row number '%d' is out of range", row)
	}
	return nil
}
<|endoftext|>


<commit_before>package variables

import (
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

var (
	NonInstallCommands = []string{"ls", "rm", "version", "init", "--help", "-h"}
	DefaultInstall     = filepath.Join(os.Getenv("HOME"), ".eclectica/bin")
)

func Prefix(name string) string {
	return filepath.Join(Home(), name)
}

// TempDir gets OS consistent folder path
// I am crying over here :/
func TempDir() (tmp string) {
	tmp = os.TempDir()
	if runtime.GOOS == "linux" {
		tmp += "/"
	}

	return
}

// IsDebug checks if eclectica in the debug state
// i.e. will print more info when executing commands
func IsDebug() bool {
	return os.Getenv("DEBUG") == "true"
}

func nameAndVersion(args []interface{}) (string, string) {
	var (
		name    = args[0].(string)
		version string
	)

	if len(args) == 2 {
		version = args[1].(string)
	} else {
		version = "current"
	}

	return name, version
}

// Path gives full path to parent of "bin" folder
func Path(args ...interface{}) string {
	name, version := nameAndVersion(args)

	return filepath.Join(Home(), name, version)
}

func GetBin(args ...interface{}) string {
	name, version := nameAndVersion(args)

	base := Path(name, version)

	// TODO: fix
	if name == "rust" {
		name = "rustc"
	}

	return filepath.Join(base, "bin", name)
}

func GetShellName() string {
	name := os.Getenv("SHELL")
	path := strings.Split(name, "/")

	return path[len(path)-1]
}

func Base() string {
	return filepath.Join(os.Getenv("HOME"), ".eclectica")
}

func Home() string {
	return filepath.Join(Base(), "versions")
}
<commit_msg>Assume `bash` shell if `SHELL` environment variable is not set<commit_after>package variables

import (
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

var (
	NonInstallCommands = []string{"ls", "rm", "version", "init", "--help", "-h"}
	DefaultInstall     = filepath.Join(os.Getenv("HOME"), ".eclectica/bin")
)

func Prefix(name string) string {
	return filepath.Join(Home(), name)
}

// TempDir gets OS consistent folder path
// I am crying over here :/
func TempDir() (tmp string) {
	tmp = os.TempDir()
	if runtime.GOOS == "linux" {
		tmp += "/"
	}

	return
}

// IsDebug checks if eclectica in the debug state
// i.e. will print more info when executing commands
func IsDebug() bool {
	return os.Getenv("DEBUG") == "true"
}

func nameAndVersion(args []interface{}) (string, string) {
	var (
		name    = args[0].(string)
		version string
	)

	if len(args) == 2 {
		version = args[1].(string)
	} else {
		version = "current"
	}

	return name, version
}

// Path gives full path to parent of "bin" folder
func Path(args ...interface{}) string {
	name, version := nameAndVersion(args)

	return filepath.Join(Home(), name, version)
}

func GetBin(args ...interface{}) string {
	name, version := nameAndVersion(args)

	base := Path(name, version)

	// TODO: fix
	if name == "rust" {
		name = "rustc"
	}

	return filepath.Join(base, "bin", name)
}

func GetShellName() string {
	name := os.Getenv("SHELL")
	path := strings.Split(name, "/")
	intermidiate := path[len(path)-1]

	// Since $SHELL might not be set, assume bash otherwise
	if len(intermidiate) == 0 {
		return "bash"
	}

	return intermidiate
}

func Base() string {
	return filepath.Join(os.Getenv("HOME"), ".eclectica")
}

func Home() string {
	return filepath.Join(Base(), "versions")
}
<|endoftext|>


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

package gob_test

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
)

// The Vector type has unexported fields, which the package cannot access.
// We therefore write a BinaryMarshal/BinaryUnmarshal method pair to allow us
// to send and receive the type with the gob package. These interfaces are
// defined in the "encoding" package.
// We could equivalently use the locally defined GobEncode/GobDecoder
// interfaces.
type Vector struct {
	x, y, z int
}

func (v Vector) MarshalBinary() ([]byte, error) {
	// A simple encoding: plain text.
	var b bytes.Buffer
	fmt.Fprintln(&b, v.x, v.y, v.z)
	return b.Bytes(), nil
}

// UnmarshalBinary modifies the receiver so it must take a pointer receiver.
func (v *Vector) UnmarshalBinary(data []byte) error {
	// A simple encoding: plain text.
	b := bytes.NewBuffer(data)
	_, err := fmt.Fscanln(b, &v.x, &v.y, &v.z)
	return err
}

// This example transmits a value that implements the custom encoding and decoding methods.
func Example_gob_encode_decode() {
	var network bytes.Buffer // Stand-in for the network.

	// Create an encoder and send a value.
	enc := gob.NewEncoder(&network)
	err := enc.Encode(Vector{3, 4, 5})
	if err != nil {
		log.Fatal("encode:", err)
	}

	// Create a decoder and receive a value.
	dec := gob.NewDecoder(&network)
	var v Vector
	err = dec.Decode(&v)
	if err != nil {
		log.Fatal("decode:", err)
	}
	fmt.Println(v)

	// Output:
	// {3 4 5}
}
<commit_msg>encoding/gob: expose encode/decode example<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gob_test

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
)

// The Vector type has unexported fields, which the package cannot access.
// We therefore write a BinaryMarshal/BinaryUnmarshal method pair to allow us
// to send and receive the type with the gob package. These interfaces are
// defined in the "encoding" package.
// We could equivalently use the locally defined GobEncode/GobDecoder
// interfaces.
type Vector struct {
	x, y, z int
}

func (v Vector) MarshalBinary() ([]byte, error) {
	// A simple encoding: plain text.
	var b bytes.Buffer
	fmt.Fprintln(&b, v.x, v.y, v.z)
	return b.Bytes(), nil
}

// UnmarshalBinary modifies the receiver so it must take a pointer receiver.
func (v *Vector) UnmarshalBinary(data []byte) error {
	// A simple encoding: plain text.
	b := bytes.NewBuffer(data)
	_, err := fmt.Fscanln(b, &v.x, &v.y, &v.z)
	return err
}

// This example transmits a value that implements the custom encoding and decoding methods.
func Example_encodeDecode() {
	var network bytes.Buffer // Stand-in for the network.

	// Create an encoder and send a value.
	enc := gob.NewEncoder(&network)
	err := enc.Encode(Vector{3, 4, 5})
	if err != nil {
		log.Fatal("encode:", err)
	}

	// Create a decoder and receive a value.
	dec := gob.NewDecoder(&network)
	var v Vector
	err = dec.Decode(&v)
	if err != nil {
		log.Fatal("decode:", err)
	}
	fmt.Println(v)

	// Output:
	// {3 4 5}
}
<|endoftext|>


<commit_before>package dskvs

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"runtime"
	"strconv"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

type Data struct {
	Descr string
}

/*
	Boilerplate
*/

func setUp(t *testing.T) *Store {
	store, err := NewStore("./db")
	if err != nil {
		t.Fatalf("Error creating store, %v", err)
	}

	err = store.Load()
	if err != nil {
		t.Fatalf("Error loading store, %v", err)
	}
	return store
}

func tearDown(store *Store, t *testing.T) {
	err := store.Close()
	if err != nil {
		t.Fatalf("Error closing store, %v", err)
	}
	err = os.RemoveAll(store.storagePath)
	if err != nil {
		t.Fatalf("Error deleting storage path, %v", err)
	}
}

func generateData(d Data, t *testing.T) []byte {
	dataBytes, err := json.Marshal(d)
	if err != nil {
		t.Fatal("Error with test data, %v", err)
	}
	return dataBytes
}

/*
	Common checks
*/

func checkGetIsEmpty(store *Store, key string, t *testing.T) {
	_, err := store.Get(key)
	if err == nil {
		t.Fatalf("Expected to receive KeyError but no error")
	}
	switch err := err.(type) {
	case KeyError:
		// Expected
		break
	default:
		t.Fatalf("Expected to receive KeyError but got <%s>", err)
	}
}

/*
	Test cases, single goroutine
*/

func TestCreatingStore(t *testing.T) {
	store := setUp(t)
	tearDown(store, t)
}

func TestSingleOperation(t *testing.T) {

	store := setUp(t)
	defer tearDown(store, t)

	key := "artist/daftpunk"
	expected := generateData(Data{"The peak of awesome"}, t)

	err := store.Put(key, expected)
	if err != nil {
		t.Fatalf("Error putting data in", err)
	}

	actual, err := store.Get(key)
	if err != nil {
		t.Fatalf("Error getting data back", err)
	}

	if !bytes.Equal(expected, actual) {
		t.Fatalf("Expected <%s> but was <%s>",
			expected,
			actual)
	}

	err = store.Delete(key)
	if err != nil {
		t.Fatalf("Error deleting data we just put", err)
	}

	checkGetIsEmpty(store, key, t)

}

func TestMultipleOperations(t *testing.T) {

	store := setUp(t)
	defer tearDown(store, t)

	coll := "artist"
	baseKey := "daftpunk"

	var key string
	var expected []byte
	var expectedList [][]byte
	var actual []byte
	for i := int(0); i < 10; i++ {
		key = coll + CollKeySep + baseKey + strconv.Itoa(i)
		expected = generateData(Data{"The peak of awesome" + strconv.Itoa(i)}, t)

		err := store.Put(key, expected)
		if err != nil {
			t.Errorf("Error putting data in", err)
		}
		actual, err = store.Get(key)
		if err != nil {
			t.Errorf("Error getting data back", err)
		}

		if !bytes.Equal(expected, actual) {
			t.Errorf("Expected <%s> but was <%s>",
				expected,
				actual)
		}

		expectedList = append(expectedList, expected)
	}

	actuaList, err := store.GetAll(coll)
	if err != nil {
		t.Fatalf("Error getting all keys with coll <%s>", coll)
	}
	if len(actuaList) != len(expectedList) {
		t.Fatalf("Expected to read %d items, but read %d",
			len(expectedList),
			len(actuaList))
	}

	err = store.DeleteAll("artist")
	if err != nil {
		t.Fatal("Error deleting key", err)
	}
	for i := int(0); i < 10; i++ {
		key = coll + CollKeySep + baseKey + strconv.Itoa(i)
		checkGetIsEmpty(store, key, t)
	}
}

/*
	Test cases, multiple goroutine
*/

func TestMultipleGoroutine(t *testing.T) {
	var kvCount int
	if testing.Short() {
		kvCount = 1000
	} else {
		kvCount = 10000
	}
	coll := "games"
	baseKey := "total annihilation #"

	type KeyValue struct {
		Key   string
		Value []byte
	}
	countGet := int64(0)
	countPut := int64(0)
	countDelete := int64(0)
	start := time.Now()

	store := setUp(t)
	defer tearDown(store, t)
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))

	var writeGroup sync.WaitGroup
	var readGroup sync.WaitGroup
	var deleteGroup sync.WaitGroup
	writeErr := make(chan error)
	readErr := make(chan error)
	deleteErr := make(chan error)

	expectedList := make([]KeyValue, 0)
	for i := int(0); i < kvCount; i++ {
		key := coll + CollKeySep + baseKey + strconv.Itoa(i)
		data := generateData(Data{"It's fun!"}, t)
		kv := KeyValue{key, data}
		expectedList = append(expectedList, kv)
	}

	for _, kv := range expectedList {
		// Put all the values concurrently, ensure there's no error
		writeGroup.Add(1)
		go func(pair KeyValue, cErr chan error) {
			defer writeGroup.Done()
			err := store.Put(pair.Key, pair.Value)
			atomic.AddInt64(&countPut, 1)
			if err != nil {
				t.Fatal("Received an error", err)
				cErr <- err
			}

		}(kv, writeErr)
	}

	writeGroup.Wait()

	if len(writeErr) != 0 {
		t.Fatalf("Failed to write values concurrently, got %d errors",
			len(writeErr))
	}

	for _, kv := range expectedList {
		// Get each value, ensure they're the same
		readGroup.Add(1)
		go func(kv KeyValue, cErr chan error) {
			defer readGroup.Done()

			expected := kv.Value
			actual, err := store.Get(kv.Key)
			atomic.AddInt64(&countGet, 1)
			if err != nil {
				cErr <- err
			} else if !bytes.Equal(expected, actual) {
				t.Errorf("Expected <%s> but was <%s>",
					expected,
					actual)
			}

		}(kv, readErr)

		// GetAll values, ensure this slice is as big as all the value
		// we've put
		readGroup.Add(1)
		go func(cErr chan error) {
			defer readGroup.Done()

			actual, err := store.GetAll(coll)
			atomic.AddInt64(&countGet, int64(len(actual)))
			if err != nil {
				cErr <- err
			} else if len(actual) != len(expectedList) {
				t.Errorf("Expected len(store.GetAll)=<%s> but was <%s>",
					len(expectedList),
					len(actual))
			}
		}(readErr)
	}

	readGroup.Wait()

	if len(readErr) != 0 {
		t.Fatalf("Failed to write values concurrently, got %d errors",
			len(readErr))
	}

	for _, kv := range expectedList {
		// Put all the values concurrently, ensure there's no error
		deleteGroup.Add(1)
		go func(pair KeyValue, cErr chan error) {
			defer deleteGroup.Done()
			err := store.Delete(pair.Key)
			atomic.AddInt64(&countDelete, 1)
			if err != nil {
				t.Fatal("Received an error", err)
				cErr <- err
			}

		}(kv, deleteErr)
	}

	deleteGroup.Wait()

	if len(deleteErr) != 0 {
		t.Fatalf("Failed to write values concurrently, got %d errors",
			len(deleteErr))
	}

	err := store.DeleteAll(coll)
	atomic.AddInt64(&countDelete, int64(len(expectedList)))
	if err != nil {
		t.Fatalf("Error deleting all", err)
	}
	for _, kv := range expectedList {
		atomic.AddInt64(&countGet, 1)
		checkGetIsEmpty(store, kv.Key, t)
	}
	duration := time.Now().Sub(start)
	fmt.Printf("Concurrent test\n")
	fmt.Printf("- Get operations=%d\n", countGet)
	fmt.Printf("- Put operations=%d\n", countPut)
	fmt.Printf("- Delete operations=%d\n", countPut)
	fmt.Printf("in %fs ", duration.Seconds())
	fmt.Printf("by %d cpus, using %d concurrent goroutines\n",
		runtime.NumCPU(), kvCount)

}
<commit_msg>Add failing test for persistence artifact reload.<commit_after>package dskvs

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"runtime"
	"strconv"
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

type Data struct {
	Descr string
}

/*
	Boilerplate
*/

func setUp(t *testing.T) *Store {
	store, err := NewStore("./db")
	if err != nil {
		t.Fatalf("Error creating store, %v", err)
	}

	err = store.Load()
	if err != nil {
		t.Fatalf("Error loading store, %v", err)
	}
	return store
}

func tearDown(store *Store, t *testing.T) {
	err := store.Close()
	if err != nil {
		t.Fatalf("Error closing store, %v", err)
	}
	err = os.RemoveAll(store.storagePath)
	if err != nil {
		t.Fatalf("Error deleting storage path, %v", err)
	}
}

func generateData(d Data, t *testing.T) []byte {
	dataBytes, err := json.Marshal(d)
	if err != nil {
		t.Fatal("Error with test data, %v", err)
	}
	return dataBytes
}

/*
	Common checks
*/

func checkGetIsEmpty(store *Store, key string, t *testing.T) {
	_, err := store.Get(key)
	if err == nil {
		t.Fatalf("Expected to receive KeyError but no error")
	}
	switch err := err.(type) {
	case KeyError:
		// Expected
		break
	default:
		t.Fatalf("Expected to receive KeyError but got <%s>", err)
	}
}

/*
	Test cases, single goroutine
*/

func TestCreatingStore(t *testing.T) {
	store := setUp(t)
	tearDown(store, t)
}

func TestSingleOperation(t *testing.T) {

	store := setUp(t)
	defer tearDown(store, t)

	key := "artist/daftpunk"
	expected := generateData(Data{"The peak of awesome"}, t)

	err := store.Put(key, expected)
	if err != nil {
		t.Fatalf("Error putting data in, %v", err)
	}

	actual, err := store.Get(key)
	if err != nil {
		t.Fatalf("Error getting data back, %v", err)
	}

	if !bytes.Equal(expected, actual) {
		t.Fatalf("Expected <%s> but was <%s>",
			expected,
			actual)
	}

	err = store.Delete(key)
	if err != nil {
		t.Fatalf("Error deleting data we just put, %v", err)
	}

	checkGetIsEmpty(store, key, t)

}

func TestMultipleOperations(t *testing.T) {

	store := setUp(t)
	defer tearDown(store, t)

	coll := "artist"
	baseKey := "daftpunk"

	var key string
	var expected []byte
	var expectedList [][]byte
	var actual []byte
	for i := int(0); i < 10; i++ {
		key = coll + CollKeySep + baseKey + strconv.Itoa(i)
		expected = generateData(Data{"The peak of awesome" + strconv.Itoa(i)}, t)

		err := store.Put(key, expected)
		if err != nil {
			t.Errorf("Error putting data in, %v", err)
		}
		actual, err = store.Get(key)
		if err != nil {
			t.Errorf("Error getting data back, %v", err)
		}

		if !bytes.Equal(expected, actual) {
			t.Errorf("Expected <%s> but was <%s>",
				expected,
				actual)
		}

		expectedList = append(expectedList, expected)
	}

	actuaList, err := store.GetAll(coll)
	if err != nil {
		t.Fatalf("Error getting all keys with coll <%s>", coll)
	}
	if len(actuaList) != len(expectedList) {
		t.Fatalf("Expected to read %d items, but read %d",
			len(expectedList),
			len(actuaList))
	}

	err = store.DeleteAll("artist")
	if err != nil {
		t.Fatal("Error deleting key, %v", err)
	}
	for i := int(0); i < 10; i++ {
		key = coll + CollKeySep + baseKey + strconv.Itoa(i)
		checkGetIsEmpty(store, key, t)
	}
}

func TestStorePersistPutAfterClose(t *testing.T) {
	store := setUp(t)

	key := "artist/the prodigy"
	expected := generateData(Data{"Beyond the peak"}, t)

	if err := store.Put(key, expected); err != nil {
		tearDown(store, t)
		t.Fatalf("Error putting data in, %v", err)
	}

	// Don't use tearDown as it deletes the storage after use
	if err := store.Close(); err != nil {
		t.Fatalf("Error closing store, %v", err)
	}

	otherStore := setUp(t)
	defer tearDown(otherStore, t)

	actual, err := otherStore.Get(key)
	if err != nil {
		t.Fatalf("Error getting data back, %v", err)
	}
	if !bytes.Equal(expected, actual) {
		t.Fatalf("Expected <%s> but was <%s>",
			expected,
			actual)
	}
}

func TestStorePersistDeleteAfterClose(t *testing.T) {
	store := setUp(t)

	key := "artist/the prodigy"
	expected := generateData(Data{"Beyond the peak"}, t)

	if err := store.Put(key, expected); err != nil {
		tearDown(store, t)
		t.Fatalf("Error putting value in, %v", err)
	}

	if err := store.Delete(key); err != nil {
		tearDown(store, t)
		t.Fatalf("Error deleting value, %v", err)
	}

	// Don't use tearDown as it deletes the storage after use
	if err := store.Close(); err != nil {
		t.Fatalf("Error closing store, %v", err)
	}

	otherStore := setUp(t)
	defer tearDown(otherStore, t)

	checkGetIsEmpty(otherStore, key, t)

}

/*
	Test cases, multiple goroutine
*/

func TestMultipleGoroutine(t *testing.T) {
	var kvCount int
	if testing.Short() {
		kvCount = 1000
	} else {
		kvCount = 10000
	}
	coll := "games"
	baseKey := "total annihilation #"

	type KeyValue struct {
		Key   string
		Value []byte
	}
	countGet := int64(0)
	countPut := int64(0)
	countDelete := int64(0)
	start := time.Now()

	store := setUp(t)
	defer tearDown(store, t)
	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))

	var writeGroup sync.WaitGroup
	var readGroup sync.WaitGroup
	var deleteGroup sync.WaitGroup
	writeErr := make(chan error)
	readErr := make(chan error)
	deleteErr := make(chan error)

	expectedList := make([]KeyValue, 0)
	for i := int(0); i < kvCount; i++ {
		key := coll + CollKeySep + baseKey + strconv.Itoa(i)
		data := generateData(Data{"It's fun!"}, t)
		kv := KeyValue{key, data}
		expectedList = append(expectedList, kv)
	}

	for _, kv := range expectedList {
		// Put all the values concurrently, ensure there's no error
		writeGroup.Add(1)
		go func(pair KeyValue, cErr chan error) {
			defer writeGroup.Done()
			err := store.Put(pair.Key, pair.Value)
			atomic.AddInt64(&countPut, 1)
			if err != nil {
				t.Fatal("Received an error", err)
				cErr <- err
			}

		}(kv, writeErr)
	}

	writeGroup.Wait()

	if len(writeErr) != 0 {
		t.Fatalf("Failed to write values concurrently, got %d errors",
			len(writeErr))
	}

	for _, kv := range expectedList {
		// Get each value, ensure they're the same
		readGroup.Add(1)
		go func(kv KeyValue, cErr chan error) {
			defer readGroup.Done()

			expected := kv.Value
			actual, err := store.Get(kv.Key)
			atomic.AddInt64(&countGet, 1)
			if err != nil {
				cErr <- err
			} else if !bytes.Equal(expected, actual) {
				t.Errorf("Expected <%s> but was <%s>",
					expected,
					actual)
			}

		}(kv, readErr)

		// GetAll values, ensure this slice is as big as all the value
		// we've put
		readGroup.Add(1)
		go func(cErr chan error) {
			defer readGroup.Done()

			actual, err := store.GetAll(coll)
			atomic.AddInt64(&countGet, int64(len(actual)))
			if err != nil {
				cErr <- err
			} else if len(actual) != len(expectedList) {
				t.Errorf("Expected len(store.GetAll)=<%s> but was <%s>",
					len(expectedList),
					len(actual))
			}
		}(readErr)
	}

	readGroup.Wait()

	if len(readErr) != 0 {
		t.Fatalf("Failed to write values concurrently, got %d errors",
			len(readErr))
	}

	for _, kv := range expectedList {
		// Put all the values concurrently, ensure there's no error
		deleteGroup.Add(1)
		go func(pair KeyValue, cErr chan error) {
			defer deleteGroup.Done()
			err := store.Delete(pair.Key)
			atomic.AddInt64(&countDelete, 1)
			if err != nil {
				t.Fatal("Received an error", err)
				cErr <- err
			}

		}(kv, deleteErr)
	}

	deleteGroup.Wait()

	if len(deleteErr) != 0 {
		t.Fatalf("Failed to write values concurrently, got %d errors",
			len(deleteErr))
	}

	err := store.DeleteAll(coll)
	atomic.AddInt64(&countDelete, int64(len(expectedList)))
	if err != nil {
		t.Fatalf("Error deleting all", err)
	}
	for _, kv := range expectedList {
		atomic.AddInt64(&countGet, 1)
		checkGetIsEmpty(store, kv.Key, t)
	}
	duration := time.Now().Sub(start)
	fmt.Printf("Concurrent test\n")
	fmt.Printf("- Get operations=%d\n", countGet)
	fmt.Printf("- Put operations=%d\n", countPut)
	fmt.Printf("- Delete operations=%d\n", countPut)
	fmt.Printf("in %fs ", duration.Seconds())
	fmt.Printf("by %d cpus, using %d concurrent goroutines\n",
		runtime.NumCPU(), kvCount)

}
<|endoftext|>


<commit_before>// Copyright © 2016 NAME HERE <EMAIL ADDRESS>
//
// 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 cmd

import (
	"fmt"
	"os"

	"github.com/eddyzags/kafkactl/api/client"
	"github.com/eddyzags/kafkactl/brokers"

	"github.com/spf13/cobra"
)

var (
	brokersListAll bool
)

// brokersListCmd represents a brokersCmd subcommand
var brokersListCmd = &cobra.Command{
	Use:   "list",
	Short: "List your brokers",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {
		if apiURL == "" {
			fmt.Println("Cannot find kafka scheduler url. Using default: 0.0.0.0:7070")
		}

		c := client.NewClient(apiURL)

		if err := brokers.List(c, brokersListAll); err != nil {
			fmt.Fprintf(os.Stderr, "kafkactl: Unexpected error occured \"%v\"\n", err)
			os.Exit(1)
		}
	},
}

func init() {
	brokersCmd.AddCommand(brokersListCmd)
}
<commit_msg>cmd: brokers add cli handling function implemented & minor changes for broker list parameter<commit_after>// Copyright © 2016 NAME HERE <EMAIL ADDRESS>
//
// 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 cmd

import (
	"fmt"
	"os"

	"github.com/eddyzags/kafkactl/api/client"
	"github.com/eddyzags/kafkactl/brokers"

	"github.com/spf13/cobra"
)

var (
	brokersListAll bool
)

// brokersListCmd represents a brokersCmd subcommand
var brokersListCmd = &cobra.Command{
	Use:   "list",
	Short: "List your brokers",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {
		if apiURL == "" {
			fmt.Println("Cannot find kafka scheduler url. Using default: 0.0.0.0:7070")
		}

		c := client.NewClient(apiURL)

		if err := brokers.List(c, brokersListAll); err != nil {
			fmt.Fprintf(os.Stderr, "kafkactl: Unexpected error occured \"%v\"\n", err)
			os.Exit(1)
		}
	},
}

func init() {
	brokersCmd.AddCommand(brokersListCmd)

	brokersListCmd.PersistentFlags().BoolVarP(&brokersListAll, "all", "a", false, "Do not ignore unactive brokers")
}
<|endoftext|>