<commit_before>// +build windows

/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package subpath

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"k8s.io/klog/v2"
	"k8s.io/utils/mount"
	"k8s.io/utils/nsenter"
)

type subpath struct{}

// New returns a subpath.Interface for the current system
func New(mount.Interface) Interface {
	return &subpath{}
}

// NewNSEnter is to satisfy the compiler for having NewSubpathNSEnter exist for all
// OS choices. however, NSEnter is only valid on Linux
func NewNSEnter(mounter mount.Interface, ne *nsenter.Nsenter, rootDir string) Interface {
	return nil
}

// evalPath returns the path name after the evaluation of any symbolic links.
// If the path after evaluation starts with Volume or \??\Volume, it means that it was a symlink from
// volume (represented by volumeID) to the given path. In this case, the given path is returned.
func evalPath(path string) (linkedPath string, err error) {
	cmd := fmt.Sprintf("Get-Item -Path %s | Select-Object -ExpandProperty Target", path)
	output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
	klog.V(4).Infof("evaluate symlink from %s: %s %v", path, string(output), err)
	if err != nil {
		return "", err
	}
	linkedPath = strings.TrimSpace(string(output))
	if isVolumePrefix(linkedPath) {
		return path, err
	}
	return linkedPath, err
}

// isVolumePrefix returns true if the given path name starts with "Volume" or volume prefix including
// "\\.\" or "\\?\". Otherwise, it returns false.
func isVolumePrefix(path string) bool {
	if strings.HasPrefix(path, "Volume") || strings.HasPrefix(path, "\\\\?\\") || strings.HasPrefix(path, "\\\\.\\") {
		return true
	}
	return false
}

// check whether hostPath is within volume path
// this func will lock all intermediate subpath directories, need to close handle outside of this func after container started
func lockAndCheckSubPath(volumePath, hostPath string) ([]uintptr, error) {
	if len(volumePath) == 0 || len(hostPath) == 0 {
		return []uintptr{}, nil
	}

	finalSubPath, err := evalPath(hostPath)
	if err != nil {
		return []uintptr{}, fmt.Errorf("cannot evaluate link %s: %s", hostPath, err)
	}

	finalVolumePath, err := evalPath(volumePath)
	if err != nil {
		return []uintptr{}, fmt.Errorf("cannot read link %s: %s", volumePath, err)
	}

	return lockAndCheckSubPathWithoutSymlink(finalVolumePath, finalSubPath)
}

// lock all intermediate subPath directories and check they are all within volumePath
// volumePath & subPath should not contain any symlink, otherwise it will return error
func lockAndCheckSubPathWithoutSymlink(volumePath, subPath string) ([]uintptr, error) {
	if len(volumePath) == 0 || len(subPath) == 0 {
		return []uintptr{}, nil
	}

	// get relative path to volumePath
	relSubPath, err := filepath.Rel(volumePath, subPath)
	if err != nil {
		return []uintptr{}, fmt.Errorf("Rel(%s, %s) error: %v", volumePath, subPath, err)
	}
	if mount.StartsWithBackstep(relSubPath) {
		return []uintptr{}, fmt.Errorf("SubPath %q not within volume path %q", subPath, volumePath)
	}

	if relSubPath == "." {
		// volumePath and subPath are equal
		return []uintptr{}, nil
	}

	fileHandles := []uintptr{}
	var errorResult error

	currentFullPath := volumePath
	dirs := strings.Split(relSubPath, string(os.PathSeparator))
	for _, dir := range dirs {
		// lock intermediate subPath directory first
		currentFullPath = filepath.Join(currentFullPath, dir)
		handle, err := lockPath(currentFullPath)
		if err != nil {
			errorResult = fmt.Errorf("cannot lock path %s: %s", currentFullPath, err)
			break
		}
		fileHandles = append(fileHandles, handle)

		// make sure intermediate subPath directory does not contain symlink any more
		stat, err := os.Lstat(currentFullPath)
		if err != nil {
			errorResult = fmt.Errorf("Lstat(%q) error: %v", currentFullPath, err)
			break
		}
		if stat.Mode()&os.ModeSymlink != 0 {
			errorResult = fmt.Errorf("subpath %q is an unexpected symlink after EvalSymlinks", currentFullPath)
			break
		}

		if !mount.PathWithinBase(currentFullPath, volumePath) {
			errorResult = fmt.Errorf("SubPath %q not within volume path %q", currentFullPath, volumePath)
			break
		}
	}

	return fileHandles, errorResult
}

// unlockPath unlock directories
func unlockPath(fileHandles []uintptr) {
	if fileHandles != nil {
		for _, handle := range fileHandles {
			syscall.CloseHandle(syscall.Handle(handle))
		}
	}
}

// lockPath locks a directory or symlink, return handle, exec "syscall.CloseHandle(handle)" to unlock the path
func lockPath(path string) (uintptr, error) {
	if len(path) == 0 {
		return uintptr(syscall.InvalidHandle), syscall.ERROR_FILE_NOT_FOUND
	}
	pathp, err := syscall.UTF16PtrFromString(path)
	if err != nil {
		return uintptr(syscall.InvalidHandle), err
	}
	access := uint32(syscall.GENERIC_READ)
	sharemode := uint32(syscall.FILE_SHARE_READ)
	createmode := uint32(syscall.OPEN_EXISTING)
	flags := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
	fd, err := syscall.CreateFile(pathp, access, sharemode, nil, createmode, flags, 0)
	return uintptr(fd), err
}

// Lock all directories in subPath and check they're not symlinks.
func (sp *subpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
	handles, err := lockAndCheckSubPath(subPath.VolumePath, subPath.Path)

	// Unlock the directories when the container starts
	cleanupAction = func() {
		unlockPath(handles)
	}
	return subPath.Path, cleanupAction, err
}

// No bind-mounts for subpaths are necessary on Windows
func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
	return nil
}

// SafeMakeDir makes sure that the created directory does not escape given base directory mis-using symlinks.
func (sp *subpath) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
	realBase, err := evalPath(base)
	if err != nil {
		return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
	}

	realFullPath := filepath.Join(realBase, subdir)
	return doSafeMakeDir(realFullPath, realBase, perm)
}

func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
	klog.V(4).Infof("Creating directory %q within base %q", pathname, base)

	if !mount.PathWithinBase(pathname, base) {
		return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
	}

	// Quick check if the directory already exists
	s, err := os.Stat(pathname)
	if err == nil {
		// Path exists
		if s.IsDir() {
			// The directory already exists. It can be outside of the parent,
			// but there is no race-proof check.
			klog.V(4).Infof("Directory %s already exists", pathname)
			return nil
		}
		return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
	}

	// Find all existing directories
	existingPath, toCreate, err := findExistingPrefix(base, pathname)
	if err != nil {
		return fmt.Errorf("error opening directory %s: %s", pathname, err)
	}
	if len(toCreate) == 0 {
		return nil
	}

	// Ensure the existing directory is inside allowed base
	fullExistingPath, err := evalPath(existingPath)
	if err != nil {
		return fmt.Errorf("error opening existing directory %s: %s", existingPath, err)
	}
	fullBasePath, err := evalPath(base)
	if err != nil {
		return fmt.Errorf("cannot read link %s: %s", base, err)
	}
	if !mount.PathWithinBase(fullExistingPath, fullBasePath) {
		return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
	}

	// lock all intermediate directories from fullBasePath to fullExistingPath (top to bottom)
	fileHandles, err := lockAndCheckSubPathWithoutSymlink(fullBasePath, fullExistingPath)
	defer unlockPath(fileHandles)
	if err != nil {
		return err
	}

	klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
	currentPath := fullExistingPath
	// create the directories one by one, making sure nobody can change
	// created directory into symlink by lock that directory immediately
	for _, dir := range toCreate {
		currentPath = filepath.Join(currentPath, dir)
		klog.V(4).Infof("Creating %s", dir)
		if err := os.Mkdir(currentPath, perm); err != nil {
			return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
		}
		handle, err := lockPath(currentPath)
		if err != nil {
			return fmt.Errorf("cannot lock path %s: %s", currentPath, err)
		}
		defer syscall.CloseHandle(syscall.Handle(handle))
		// make sure newly created directory does not contain symlink after lock
		stat, err := os.Lstat(currentPath)
		if err != nil {
			return fmt.Errorf("Lstat(%q) error: %v", currentPath, err)
		}
		if stat.Mode()&os.ModeSymlink != 0 {
			return fmt.Errorf("subpath %q is an unexpected symlink after Mkdir", currentPath)
		}
	}

	return nil
}

// findExistingPrefix finds prefix of pathname that exists. In addition, it
// returns list of remaining directories that don't exist yet.
func findExistingPrefix(base, pathname string) (string, []string, error) {
	rel, err := filepath.Rel(base, pathname)
	if err != nil {
		return base, nil, err
	}

	if mount.StartsWithBackstep(rel) {
		return base, nil, fmt.Errorf("pathname(%s) is not within base(%s)", pathname, base)
	}

	if rel == "." {
		// base and pathname are equal
		return pathname, []string{}, nil
	}

	dirs := strings.Split(rel, string(filepath.Separator))

	var parent string
	currentPath := base
	for i, dir := range dirs {
		parent = currentPath
		currentPath = filepath.Join(parent, dir)
		if _, err := os.Lstat(currentPath); err != nil {
			if os.IsNotExist(err) {
				return parent, dirs[i:], nil
			}
			return base, nil, err
		}
	}

	return pathname, []string{}, nil
}
<commit_msg>Windows: Fixes subpath symlink evaluation<commit_after>// +build windows

/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package subpath

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	"k8s.io/klog/v2"
	"k8s.io/utils/mount"
	"k8s.io/utils/nsenter"
)

type subpath struct{}

// New returns a subpath.Interface for the current system
func New(mount.Interface) Interface {
	return &subpath{}
}

// NewNSEnter is to satisfy the compiler for having NewSubpathNSEnter exist for all
// OS choices. however, NSEnter is only valid on Linux
func NewNSEnter(mounter mount.Interface, ne *nsenter.Nsenter, rootDir string) Interface {
	return nil
}

// evalPath returns the path name after the evaluation of any symbolic links.
// If the path after evaluation starts with Volume or \??\Volume, it means that it was a symlink from
// volume (represented by volumeID) to the given path. In this case, the given path is returned.
func evalPath(path string) (linkedPath string, err error) {
	cmd := fmt.Sprintf("Get-Item -Path %s | Select-Object -ExpandProperty Target", path)
	output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
	klog.V(4).Infof("evaluate symlink from %s: %s %v", path, string(output), err)
	if err != nil {
		return "", err
	}
	linkedPath = strings.TrimSpace(string(output))
	if linkedPath == "" {
		klog.V(4).Infof("Path '%s' has no target. Consiering it as evaluated.", path)
		return path, nil
	}
	if isVolumePrefix(linkedPath) {
		return path, err
	}
	return linkedPath, err
}

// isVolumePrefix returns true if the given path name starts with "Volume" or volume prefix including
// "\\.\" or "\\?\". Otherwise, it returns false.
func isVolumePrefix(path string) bool {
	if strings.HasPrefix(path, "Volume") || strings.HasPrefix(path, "\\\\?\\") || strings.HasPrefix(path, "\\\\.\\") {
		return true
	}
	return false
}

// check whether hostPath is within volume path
// this func will lock all intermediate subpath directories, need to close handle outside of this func after container started
func lockAndCheckSubPath(volumePath, hostPath string) ([]uintptr, error) {
	if len(volumePath) == 0 || len(hostPath) == 0 {
		return []uintptr{}, nil
	}

	finalSubPath, err := evalPath(hostPath)
	if err != nil {
		return []uintptr{}, fmt.Errorf("cannot evaluate link %s: %s", hostPath, err)
	}

	finalVolumePath, err := evalPath(volumePath)
	if err != nil {
		return []uintptr{}, fmt.Errorf("cannot read link %s: %s", volumePath, err)
	}

	return lockAndCheckSubPathWithoutSymlink(finalVolumePath, finalSubPath)
}

// lock all intermediate subPath directories and check they are all within volumePath
// volumePath & subPath should not contain any symlink, otherwise it will return error
func lockAndCheckSubPathWithoutSymlink(volumePath, subPath string) ([]uintptr, error) {
	if len(volumePath) == 0 || len(subPath) == 0 {
		return []uintptr{}, nil
	}

	// get relative path to volumePath
	relSubPath, err := filepath.Rel(volumePath, subPath)
	if err != nil {
		return []uintptr{}, fmt.Errorf("Rel(%s, %s) error: %v", volumePath, subPath, err)
	}
	if mount.StartsWithBackstep(relSubPath) {
		return []uintptr{}, fmt.Errorf("SubPath %q not within volume path %q", subPath, volumePath)
	}

	if relSubPath == "." {
		// volumePath and subPath are equal
		return []uintptr{}, nil
	}

	fileHandles := []uintptr{}
	var errorResult error

	currentFullPath := volumePath
	dirs := strings.Split(relSubPath, string(os.PathSeparator))
	for _, dir := range dirs {
		// lock intermediate subPath directory first
		currentFullPath = filepath.Join(currentFullPath, dir)
		handle, err := lockPath(currentFullPath)
		if err != nil {
			errorResult = fmt.Errorf("cannot lock path %s: %s", currentFullPath, err)
			break
		}
		fileHandles = append(fileHandles, handle)

		// make sure intermediate subPath directory does not contain symlink any more
		stat, err := os.Lstat(currentFullPath)
		if err != nil {
			errorResult = fmt.Errorf("Lstat(%q) error: %v", currentFullPath, err)
			break
		}
		if stat.Mode()&os.ModeSymlink != 0 {
			errorResult = fmt.Errorf("subpath %q is an unexpected symlink after EvalSymlinks", currentFullPath)
			break
		}

		if !mount.PathWithinBase(currentFullPath, volumePath) {
			errorResult = fmt.Errorf("SubPath %q not within volume path %q", currentFullPath, volumePath)
			break
		}
	}

	return fileHandles, errorResult
}

// unlockPath unlock directories
func unlockPath(fileHandles []uintptr) {
	if fileHandles != nil {
		for _, handle := range fileHandles {
			syscall.CloseHandle(syscall.Handle(handle))
		}
	}
}

// lockPath locks a directory or symlink, return handle, exec "syscall.CloseHandle(handle)" to unlock the path
func lockPath(path string) (uintptr, error) {
	if len(path) == 0 {
		return uintptr(syscall.InvalidHandle), syscall.ERROR_FILE_NOT_FOUND
	}
	pathp, err := syscall.UTF16PtrFromString(path)
	if err != nil {
		return uintptr(syscall.InvalidHandle), err
	}
	access := uint32(syscall.GENERIC_READ)
	sharemode := uint32(syscall.FILE_SHARE_READ)
	createmode := uint32(syscall.OPEN_EXISTING)
	flags := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
	fd, err := syscall.CreateFile(pathp, access, sharemode, nil, createmode, flags, 0)
	return uintptr(fd), err
}

// Lock all directories in subPath and check they're not symlinks.
func (sp *subpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
	handles, err := lockAndCheckSubPath(subPath.VolumePath, subPath.Path)

	// Unlock the directories when the container starts
	cleanupAction = func() {
		unlockPath(handles)
	}
	return subPath.Path, cleanupAction, err
}

// No bind-mounts for subpaths are necessary on Windows
func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
	return nil
}

// SafeMakeDir makes sure that the created directory does not escape given base directory mis-using symlinks.
func (sp *subpath) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
	realBase, err := evalPath(base)
	if err != nil {
		return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
	}

	realFullPath := filepath.Join(realBase, subdir)
	return doSafeMakeDir(realFullPath, realBase, perm)
}

func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
	klog.V(4).Infof("Creating directory %q within base %q", pathname, base)

	if !mount.PathWithinBase(pathname, base) {
		return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
	}

	// Quick check if the directory already exists
	s, err := os.Stat(pathname)
	if err == nil {
		// Path exists
		if s.IsDir() {
			// The directory already exists. It can be outside of the parent,
			// but there is no race-proof check.
			klog.V(4).Infof("Directory %s already exists", pathname)
			return nil
		}
		return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
	}

	// Find all existing directories
	existingPath, toCreate, err := findExistingPrefix(base, pathname)
	if err != nil {
		return fmt.Errorf("error opening directory %s: %s", pathname, err)
	}
	if len(toCreate) == 0 {
		return nil
	}

	// Ensure the existing directory is inside allowed base
	fullExistingPath, err := evalPath(existingPath)
	if err != nil {
		return fmt.Errorf("error opening existing directory %s: %s", existingPath, err)
	}
	fullBasePath, err := evalPath(base)
	if err != nil {
		return fmt.Errorf("cannot read link %s: %s", base, err)
	}
	if !mount.PathWithinBase(fullExistingPath, fullBasePath) {
		return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
	}

	// lock all intermediate directories from fullBasePath to fullExistingPath (top to bottom)
	fileHandles, err := lockAndCheckSubPathWithoutSymlink(fullBasePath, fullExistingPath)
	defer unlockPath(fileHandles)
	if err != nil {
		return err
	}

	klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
	currentPath := fullExistingPath
	// create the directories one by one, making sure nobody can change
	// created directory into symlink by lock that directory immediately
	for _, dir := range toCreate {
		currentPath = filepath.Join(currentPath, dir)
		klog.V(4).Infof("Creating %s", dir)
		if err := os.Mkdir(currentPath, perm); err != nil {
			return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
		}
		handle, err := lockPath(currentPath)
		if err != nil {
			return fmt.Errorf("cannot lock path %s: %s", currentPath, err)
		}
		defer syscall.CloseHandle(syscall.Handle(handle))
		// make sure newly created directory does not contain symlink after lock
		stat, err := os.Lstat(currentPath)
		if err != nil {
			return fmt.Errorf("Lstat(%q) error: %v", currentPath, err)
		}
		if stat.Mode()&os.ModeSymlink != 0 {
			return fmt.Errorf("subpath %q is an unexpected symlink after Mkdir", currentPath)
		}
	}

	return nil
}

// findExistingPrefix finds prefix of pathname that exists. In addition, it
// returns list of remaining directories that don't exist yet.
func findExistingPrefix(base, pathname string) (string, []string, error) {
	rel, err := filepath.Rel(base, pathname)
	if err != nil {
		return base, nil, err
	}

	if mount.StartsWithBackstep(rel) {
		return base, nil, fmt.Errorf("pathname(%s) is not within base(%s)", pathname, base)
	}

	if rel == "." {
		// base and pathname are equal
		return pathname, []string{}, nil
	}

	dirs := strings.Split(rel, string(filepath.Separator))

	var parent string
	currentPath := base
	for i, dir := range dirs {
		parent = currentPath
		currentPath = filepath.Join(parent, dir)
		if _, err := os.Lstat(currentPath); err != nil {
			if os.IsNotExist(err) {
				return parent, dirs[i:], nil
			}
			return base, nil, err
		}
	}

	return pathname, []string{}, nil
}
<|endoftext|>


<commit_before>package setup

import (
	// . "github.com/sebastianwebber/dbview/setup"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Setup database user and groups", func() {

	var (
		dbConnectionInfo = ConnectionDetails{
			userName: "sebastian",
			database: "sebastian"}
		sampleConnString = "user=sebastian dbname=sebastian"

		testUserName = "dbview"
	)

	Context("When I connect to the database", func() {

		It("Should convert a the connection info to the lib/pq connection", func() {
			Expect(dbConnectionInfo.toString()).To(Equal(sampleConnString))
		})

		It("Should not set a value in the connection string when it is empty", func() {
			Expect(ConnectionDetails{userName: "sebastian", host: ""}.toString()).To(Equal("user=sebastian"))
		})

		It("Should connect to a database", func() {
			dbConnectionInfo.sslmode = "disable"
			db, err := connect(dbConnectionInfo)
			db.Close()
			Expect(err).To(BeNil())
		})

	})

	Context("When I install the dbview at the database", func() {

		It("It check if user exists before try to create a new one", func() {
			_, err := checkIfUserExists(dbConnectionInfo, testUserName)
			Expect(err).To(BeNil())
		})

		It("Should create a user in the database if not exists", func() {
			err := CreateUser(dbConnectionInfo, testUserName, []string{"PASSWORD 'super_senha'", "SUPERUSER"})
			Expect(err).To(BeNil())
		})
	})
})
<commit_msg>fix context details<commit_after>package setup

import (
	// . "github.com/sebastianwebber/dbview/setup"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Setup database user and groups", func() {

	var (
		dbConnectionInfo = ConnectionDetails{
			userName: "sebastian",
			database: "sebastian"}
		sampleConnString = "user=sebastian dbname=sebastian"

		testUserName = "dbview"
	)

	Context("When I connect to the database", func() {

		It("Should convert a the connection info to the lib/pq connection", func() {
			Expect(dbConnectionInfo.toString()).To(Equal(sampleConnString))
		})

		It("Should not set a value in the connection string when it is empty", func() {
			Expect(ConnectionDetails{userName: "sebastian", host: ""}.toString()).To(Equal("user=sebastian"))
		})

		It("Should connect to a database", func() {
			dbConnectionInfo.sslmode = "disable"
			db, err := connect(dbConnectionInfo)
			db.Close()
			Expect(err).To(BeNil())
		})

	})

	Context("When I Create the dbview user at the database", func() {

		It("It check if user exists before try to create a new one", func() {
			_, err := checkIfUserExists(dbConnectionInfo, testUserName)
			Expect(err).To(BeNil())
		})

		It("Should create a user in the database if not exists", func() {
			err := CreateUser(dbConnectionInfo, testUserName, []string{"PASSWORD 'super_senha'", "SUPERUSER"})
			Expect(err).To(BeNil())
		})
	})
})
<|endoftext|>


<commit_before>package watch

import (
	"errors"
	"strings"
	"time"
)

// Wait is the Min/Max duration used by the Watcher
type Wait struct {
	// Min and Max are the minimum and maximum time, respectively, to wait for
	// data changes before rendering a new template to disk.
	Min, Max time.Duration
}

// ParseWait parses a string of the format `minimum(:maximum)` into a Wait
// struct.
func ParseWait(s string) (*Wait, error) {
	if len(strings.TrimSpace(s)) < 1 {
		return nil, errors.New("cannot specify empty wait interval")
	}

	parts := strings.Split(s, ":")

	var min, max time.Duration
	var err error

	if len(parts) == 1 {
		min, err = time.ParseDuration(strings.TrimSpace(parts[0]))
		if err != nil {
			return nil, err
		}

		max = 4 * min
	} else if len(parts) == 2 {
		min, err = time.ParseDuration(strings.TrimSpace(parts[0]))
		if err != nil {
			return nil, err
		}

		max, err = time.ParseDuration(strings.TrimSpace(parts[1]))
		if err != nil {
			return nil, err
		}
	} else {
		return nil, errors.New("invalid wait interval format")
	}

	if min < 0 || max < 0 {
		return nil, errors.New("cannot specify a negative wait interval")
	}

	if max < min {
		return nil, errors.New("wait interval max must be larger than min")
	}

	return &Wait{min, max}, nil
}
<commit_msg>Add a flag type for parsing a wait value<commit_after>package watch

import (
	"errors"
	"fmt"
	"strings"
	"time"
)

// Wait is the Min/Max duration used by the Watcher
type Wait struct {
	// Min and Max are the minimum and maximum time, respectively, to wait for
	// data changes before rendering a new template to disk.
	Min, Max time.Duration
}

// ParseWait parses a string of the format `minimum(:maximum)` into a Wait
// struct.
func ParseWait(s string) (*Wait, error) {
	if len(strings.TrimSpace(s)) < 1 {
		return nil, errors.New("cannot specify empty wait interval")
	}

	parts := strings.Split(s, ":")

	var min, max time.Duration
	var err error

	if len(parts) == 1 {
		min, err = time.ParseDuration(strings.TrimSpace(parts[0]))
		if err != nil {
			return nil, err
		}

		max = 4 * min
	} else if len(parts) == 2 {
		min, err = time.ParseDuration(strings.TrimSpace(parts[0]))
		if err != nil {
			return nil, err
		}

		max, err = time.ParseDuration(strings.TrimSpace(parts[1]))
		if err != nil {
			return nil, err
		}
	} else {
		return nil, errors.New("invalid wait interval format")
	}

	if min < 0 || max < 0 {
		return nil, errors.New("cannot specify a negative wait interval")
	}

	if max < min {
		return nil, errors.New("wait interval max must be larger than min")
	}

	return &Wait{min, max}, nil
}

// WaitVar implements the Flag.Value interface and allows the user to specify
// a watch interval using Go's flag parsing library.
type WaitVar Wait

// Set sets the value in the format min[:max] for a wait timer.
func (w *WaitVar) Set(value string) error {
	wait, err := ParseWait(value)
	if err != nil {
		return err
	}

	w.Min = wait.Min
	w.Max = wait.Max

	return nil
}

// String returns the string format for this wait variable
func (w *WaitVar) String() string {
	return fmt.Sprintf("%s:%s", w.Min, w.Max)
}
<|endoftext|>


<commit_before>package handler

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/HouzuoGuo/laitos/inet"
	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/misc"
	"github.com/HouzuoGuo/laitos/toolbox"
)

const (
	MicrosoftBotAPITimeoutSec     = 30 // MicrosoftBotAPITimeoutSec is the timeout seconds for outgoing HTTP calls.
	MicrosoftBotCommandTimeoutSec = 30 // Command execution for Microsoft bot is constrained by this timeout.

	/*
		MicrosoftBotAPIRateLimitFactor allows (API rate limit factor * BaseRateLimit) number of requests to be made by
		Microsoft bot platform, per HTTP server rate limit interval. Be aware that API handlers place an extra rate limit
		based on incoming chat user.
		This rate limit is designed to protect brute force PIN attack from accidentally exposed API handler URL.
	*/
	MicrosoftBotAPIRateLimitFactor = 16

	/*
		MicrosoftBotUserRateLimitIntervalSec is an interval measured in number of seconds that an incoming conversation
		is allowed to invoke bot command routine. This rate limit is designed to prevent spam chats.
	*/
	MicrosoftBotUserRateLimitIntervalSec = 5
)

// MicrosoftBotJwt is a JWT returned by Microsoft bot framework.
type MicrosoftBotJwt struct {
	TokenType    string    `json:"token_type"`     // TokenType should always be "Bearer".
	ExpiresIn    int       `json:"expires_in"`     // ExpiresIn is the number of seconds till expiry.
	ExtExpiresIn int       `json:"ext_expires_in"` // ExtExpiresIn is not relevant, I do not know what it does.
	AccessToken  string    `json:"access_token"`   // AccessToken is the JWT.
	ExpiresAt    time.Time `json:"-"`              // ExpiresAt is the exact time of expiry, calculated by RetrieveJWT function.
}

// HandleMicrosoftBot serves a chat bot endpoint for Microsoft bot framework.
type HandleMicrosoftBot struct {
	ClientAppID     string `json:"ClientAppID"`     // ClientAppID is the bot's "app ID".
	ClientAppSecret string `json:"ClientAppSecret"` // ClientAppSecret is the bot's application "password".

	latestJwtMutex        *sync.Mutex     // latestJwtMutex protects latestJWT from concurrent access.
	latestJWT             MicrosoftBotJwt // latestJWT is the last retrieved JWT
	conversationRateLimit *misc.RateLimit // conversationRateLimit prevents excessively chatty conversations from taking place

	logger  lalog.Logger
	cmdProc *toolbox.CommandProcessor
}

func (hand *HandleMicrosoftBot) Initialise(logger lalog.Logger, cmdProc *toolbox.CommandProcessor, _ string) error {
	hand.logger = logger
	hand.cmdProc = cmdProc
	hand.latestJwtMutex = new(sync.Mutex)
	// Allow maximum of 1 message to be received every 5 seconds, per conversation ID.
	hand.conversationRateLimit = &misc.RateLimit{
		UnitSecs: MicrosoftBotUserRateLimitIntervalSec,
		MaxCount: 1,
		Logger:   logger,
	}
	hand.conversationRateLimit.Initialise()
	return nil
}

// RetrieveJWT asks Microsoft for a new JWT for bot API calls.
func (hand *HandleMicrosoftBot) RetrieveJWT(ctx context.Context) (MicrosoftBotJwt, error) {
	hand.latestJwtMutex.Lock()
	defer hand.latestJwtMutex.Unlock()
	if hand.latestJWT.AccessToken != "" && time.Now().Before(hand.latestJWT.ExpiresAt) {
		return hand.latestJWT, nil
	}

	hand.logger.Info("HandleMicrosoftBot.RetrieveJWT", "", nil, "attempting to renew JWT")
	httpResp, err := inet.DoHTTP(ctx, inet.HTTPRequest{
		Method:      http.MethodPost,
		ContentType: "application/x-www-form-urlencoded",
		TimeoutSec:  MicrosoftBotAPITimeoutSec,
		Body: strings.NewReader(url.Values{
			"grant_type":    []string{"client_credentials"},
			"client_id":     []string{hand.ClientAppID},
			"client_secret": []string{hand.ClientAppSecret},
			"scope":         []string{"https://api.botframework.com/.default"},
		}.Encode()),
	}, "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token")

	if err != nil {
		hand.logger.Warning("HandleMicrosoftBot.RetrieveJWT", hand.ClientAppID, err, "failed due to IO error")
		return MicrosoftBotJwt{}, err
	}
	if err = httpResp.Non2xxToError(); err != nil {
		hand.logger.Warning("HandleMicrosoftBot.RetrieveJWT", hand.ClientAppID, err, "failed due to HTTP error")
		return MicrosoftBotJwt{}, err
	}
	if err = json.Unmarshal(httpResp.Body, &hand.latestJWT); err != nil {
		hand.logger.Warning("HandleMicrosoftBot.RetrieveJWT", hand.ClientAppID, err, "failed to deserialise JWT")
		return MicrosoftBotJwt{}, err
	}
	// Exact time of expiry is simply time now + validity in seconds (ExpiresIn). Leave a second of buffer just in case.
	hand.latestJWT.ExpiresAt = time.Now().Add(time.Duration(hand.latestJWT.ExpiresIn-1) * time.Second)
	hand.logger.Info("HandleMicrosoftBot.RetrieveJWT", "", nil, "successfully renewed JWT")
	return hand.latestJWT, nil
}

// MicrosoftBotIncomingConversation is the construct of property "conversation" of MicrosoftBotIncomingChat.
type MicrosoftBotIncomingConversation struct {
	ID      string          `json:"id"`
	IsGroup json.RawMessage `json:"isGroup"`
	Name    json.RawMessage `json:"name"`
}

// MicrosoftBotIncomingChat is an "Activity object" carried by incoming chat initiated by a user to bot.
type MicrosoftBotIncomingChat struct {
	Conversation MicrosoftBotIncomingConversation `json:"conversation"` // Conversation will go into reply's "conversation" property.
	From         json.RawMessage                  `json:"from"`         // From will go into reply's "recipient" property.
	Locale       json.RawMessage                  `json:"locale"`       // Locale will go into reply's "locale" property.
	Recipient    json.RawMessage                  `json:"recipient"`    // Recipient will go into reply's "from" property.
	ID           json.RawMessage                  `json:"id"`           // ID will go into reply's "id" property.
	Text         string                           `json:"text"`         // Text is the content of incoming chat message.
	ServiceURL   string                           `json:"serviceUrl"`   // ServiceURL is the prefix name of endpoint to send chat reply to.
	Timestamp    string                           `json:"timestamp"`    // Timestamp is the timestamp of incoming chat message.
}

// MicrosoftBotReply is a message reply to be sent to user who initiated chat with bot.
type MicrosoftBotReply struct {
	Conversation MicrosoftBotIncomingConversation `json:"conversation"` // Conversation value comes from MicrosoftBotIncomingChat.
	From         json.RawMessage                  `json:"from"`         // From value comes from MicrosoftBotIncomingChat's "Recipient".
	Locale       json.RawMessage                  `json:"locale"`       // Locale value comes from MicrosoftBotIncomingChat.
	Recipient    json.RawMessage                  `json:"recipient"`    // Recipient value comes from MicrosoftBotIncomingChat's "From".
	ReplyToId    json.RawMessage                  `json:"replyToId"`    // ReplyToId  value comes from MicrosoftBotIncomingChat's "ID".
	Type         string                           `json:"type"`         // Type must be "message".
	Text         string                           `json:"text"`         // Text is the bot's response text.
}

func (hand *HandleMicrosoftBot) Handle(w http.ResponseWriter, r *http.Request) {
	NoCache(w)
	// The payload sent by Microsoft cannot possibly exceed 4 MB
	body, err := misc.ReadAllUpTo(r.Body, 4*1048576)
	if err != nil {
		hand.logger.Warning("HandleMicrosoftBot", "", err, "failed to read incoming chat HTTP request")
		http.Error(w, "failed to read request body", http.StatusBadRequest)
		return
	}
	// Deserialise chat message from incoming request
	var incoming MicrosoftBotIncomingChat
	if err := json.Unmarshal(body, &incoming); err != nil {
		hand.logger.Warning("HandleMicrosoftBot", "", err, "failed to interpret incoming chat request as JSON")
		http.Error(w, "failed to read request body in JSON", http.StatusBadRequest)
		return
	}
	// In the background, process the chat message and formulate a response.
	go func(r *http.Request) {
		convID := incoming.Conversation.ID
		if convID == "" {
			hand.logger.Warning("HandleMicrosoftBot", hand.ClientAppID, nil, "ignore conversation with empty ID")
			return
		}

		// Do not proceed if conversation is too fast
		if !hand.conversationRateLimit.Add(convID, true) {
			return
		}

		// Sometimes a chat app establishes a conversation without any content, just ignore it.
		if incoming.Text == "" {
			return
		}

		latestJWT, err := hand.RetrieveJWT(r.Context())
		if err != nil {
			hand.logger.Warning("HandleMicrosoftBot", incoming.Conversation.ID, err, "cannot reply due to JWT retrieval error")
			return
		}

		// Only process an incoming message if it arrived after server started up
		messageTime, err := time.ParseInLocation("2006-01-02T15:04:05.999999999Z", incoming.Timestamp, time.UTC)
		if err != nil {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "failed to parse timestamp \"%s\" from incoming message", incoming.Timestamp)
			return
		}
		if !messageTime.After(misc.StartupTime.UTC()) {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "ignoring message from \"%s\" that arrived before server started up", incoming.ServiceURL)
			return
		}

		// Process feature command from incoming chat text
		result := hand.cmdProc.Process(r.Context(), toolbox.Command{
			DaemonName: "httpd",
			ClientTag:  convID,
			TimeoutSec: MicrosoftBotCommandTimeoutSec,
			Content:    incoming.Text,
		}, true)

		// Most of the reply properties are directly copied from incoming request
		var reply MicrosoftBotReply
		reply.Conversation = incoming.Conversation
		reply.From = incoming.Recipient
		reply.Locale = incoming.Locale
		reply.Recipient = incoming.From
		reply.ReplyToId = incoming.ID
		reply.Type = "message"
		reply.Text = result.CombinedOutput
		replyBody, err := json.Marshal(reply)
		if err != nil {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "failed to serialise chat reply")
			return
		}
		// Send away the reply
		resp, err := inet.DoHTTP(r.Context(), inet.HTTPRequest{
			Method:      http.MethodPost,
			ContentType: "application/json",
			TimeoutSec:  MicrosoftBotAPITimeoutSec,
			Header:      http.Header{"Authorization": []string{"Bearer " + latestJWT.AccessToken}},
			Body:        bytes.NewReader(replyBody),
		}, incoming.ServiceURL+"/v3/conversations/%s/activities/%s", incoming.Conversation.ID, incoming.ID)
		if err != nil {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "failed to send chat reply due to IO error")
			return
		}
		if err = resp.Non2xxToError(); err != nil {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "failed to send chat reply due to HTTP error")
			return
		}
	}(r)
}

func (hand *HandleMicrosoftBot) GetRateLimitFactor() int {
	return MicrosoftBotAPIRateLimitFactor
}

func (hand *HandleMicrosoftBot) SelfTest() error {
	if _, err := hand.RetrieveJWT(context.Background()); err != nil {
		return fmt.Errorf("HandleMicrosoftBot encountered error: %v", err)
	}
	return nil
}
<commit_msg>When replying to microsoft bot framework conversations, use the background context. Previously it mistakenly used the incoming HTTP request context.<commit_after>package handler

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/HouzuoGuo/laitos/inet"
	"github.com/HouzuoGuo/laitos/lalog"
	"github.com/HouzuoGuo/laitos/misc"
	"github.com/HouzuoGuo/laitos/toolbox"
)

const (
	MicrosoftBotAPITimeoutSec     = 30 // MicrosoftBotAPITimeoutSec is the timeout seconds for outgoing HTTP calls.
	MicrosoftBotCommandTimeoutSec = 30 // Command execution for Microsoft bot is constrained by this timeout.

	/*
		MicrosoftBotAPIRateLimitFactor allows (API rate limit factor * BaseRateLimit) number of requests to be made by
		Microsoft bot platform, per HTTP server rate limit interval. Be aware that API handlers place an extra rate limit
		based on incoming chat user.
		This rate limit is designed to protect brute force PIN attack from accidentally exposed API handler URL.
	*/
	MicrosoftBotAPIRateLimitFactor = 16

	/*
		MicrosoftBotUserRateLimitIntervalSec is an interval measured in number of seconds that an incoming conversation
		is allowed to invoke bot command routine. This rate limit is designed to prevent spam chats.
	*/
	MicrosoftBotUserRateLimitIntervalSec = 5
)

// MicrosoftBotJwt is a JWT returned by Microsoft bot framework.
type MicrosoftBotJwt struct {
	TokenType    string    `json:"token_type"`     // TokenType should always be "Bearer".
	ExpiresIn    int       `json:"expires_in"`     // ExpiresIn is the number of seconds till expiry.
	ExtExpiresIn int       `json:"ext_expires_in"` // ExtExpiresIn is not relevant, I do not know what it does.
	AccessToken  string    `json:"access_token"`   // AccessToken is the JWT.
	ExpiresAt    time.Time `json:"-"`              // ExpiresAt is the exact time of expiry, calculated by RetrieveJWT function.
}

// HandleMicrosoftBot serves a chat bot endpoint for Microsoft bot framework.
type HandleMicrosoftBot struct {
	ClientAppID     string `json:"ClientAppID"`     // ClientAppID is the bot's "app ID".
	ClientAppSecret string `json:"ClientAppSecret"` // ClientAppSecret is the bot's application "password".

	latestJwtMutex        *sync.Mutex     // latestJwtMutex protects latestJWT from concurrent access.
	latestJWT             MicrosoftBotJwt // latestJWT is the last retrieved JWT
	conversationRateLimit *misc.RateLimit // conversationRateLimit prevents excessively chatty conversations from taking place

	logger  lalog.Logger
	cmdProc *toolbox.CommandProcessor
}

func (hand *HandleMicrosoftBot) Initialise(logger lalog.Logger, cmdProc *toolbox.CommandProcessor, _ string) error {
	hand.logger = logger
	hand.cmdProc = cmdProc
	hand.latestJwtMutex = new(sync.Mutex)
	// Allow maximum of 1 message to be received every 5 seconds, per conversation ID.
	hand.conversationRateLimit = &misc.RateLimit{
		UnitSecs: MicrosoftBotUserRateLimitIntervalSec,
		MaxCount: 1,
		Logger:   logger,
	}
	hand.conversationRateLimit.Initialise()
	return nil
}

// RetrieveJWT asks Microsoft for a new JWT for bot API calls.
func (hand *HandleMicrosoftBot) RetrieveJWT(ctx context.Context) (MicrosoftBotJwt, error) {
	hand.latestJwtMutex.Lock()
	defer hand.latestJwtMutex.Unlock()
	if hand.latestJWT.AccessToken != "" && time.Now().Before(hand.latestJWT.ExpiresAt) {
		return hand.latestJWT, nil
	}

	hand.logger.Info("HandleMicrosoftBot.RetrieveJWT", "", nil, "attempting to renew JWT")
	httpResp, err := inet.DoHTTP(ctx, inet.HTTPRequest{
		Method:      http.MethodPost,
		ContentType: "application/x-www-form-urlencoded",
		TimeoutSec:  MicrosoftBotAPITimeoutSec,
		Body: strings.NewReader(url.Values{
			"grant_type":    []string{"client_credentials"},
			"client_id":     []string{hand.ClientAppID},
			"client_secret": []string{hand.ClientAppSecret},
			"scope":         []string{"https://api.botframework.com/.default"},
		}.Encode()),
	}, "https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token")

	if err != nil {
		hand.logger.Warning("HandleMicrosoftBot.RetrieveJWT", hand.ClientAppID, err, "failed due to IO error")
		return MicrosoftBotJwt{}, err
	}
	if err = httpResp.Non2xxToError(); err != nil {
		hand.logger.Warning("HandleMicrosoftBot.RetrieveJWT", hand.ClientAppID, err, "failed due to HTTP error")
		return MicrosoftBotJwt{}, err
	}
	if err = json.Unmarshal(httpResp.Body, &hand.latestJWT); err != nil {
		hand.logger.Warning("HandleMicrosoftBot.RetrieveJWT", hand.ClientAppID, err, "failed to deserialise JWT")
		return MicrosoftBotJwt{}, err
	}
	// Exact time of expiry is simply time now + validity in seconds (ExpiresIn). Leave a second of buffer just in case.
	hand.latestJWT.ExpiresAt = time.Now().Add(time.Duration(hand.latestJWT.ExpiresIn-1) * time.Second)
	hand.logger.Info("HandleMicrosoftBot.RetrieveJWT", "", nil, "successfully renewed JWT")
	return hand.latestJWT, nil
}

// MicrosoftBotIncomingConversation is the construct of property "conversation" of MicrosoftBotIncomingChat.
type MicrosoftBotIncomingConversation struct {
	ID      string          `json:"id"`
	IsGroup json.RawMessage `json:"isGroup"`
	Name    json.RawMessage `json:"name"`
}

// MicrosoftBotIncomingChat is an "Activity object" carried by incoming chat initiated by a user to bot.
type MicrosoftBotIncomingChat struct {
	Conversation MicrosoftBotIncomingConversation `json:"conversation"` // Conversation will go into reply's "conversation" property.
	From         json.RawMessage                  `json:"from"`         // From will go into reply's "recipient" property.
	Locale       json.RawMessage                  `json:"locale"`       // Locale will go into reply's "locale" property.
	Recipient    json.RawMessage                  `json:"recipient"`    // Recipient will go into reply's "from" property.
	ID           json.RawMessage                  `json:"id"`           // ID will go into reply's "id" property.
	Text         string                           `json:"text"`         // Text is the content of incoming chat message.
	ServiceURL   string                           `json:"serviceUrl"`   // ServiceURL is the prefix name of endpoint to send chat reply to.
	Timestamp    string                           `json:"timestamp"`    // Timestamp is the timestamp of incoming chat message.
}

// MicrosoftBotReply is a message reply to be sent to user who initiated chat with bot.
type MicrosoftBotReply struct {
	Conversation MicrosoftBotIncomingConversation `json:"conversation"` // Conversation value comes from MicrosoftBotIncomingChat.
	From         json.RawMessage                  `json:"from"`         // From value comes from MicrosoftBotIncomingChat's "Recipient".
	Locale       json.RawMessage                  `json:"locale"`       // Locale value comes from MicrosoftBotIncomingChat.
	Recipient    json.RawMessage                  `json:"recipient"`    // Recipient value comes from MicrosoftBotIncomingChat's "From".
	ReplyToId    json.RawMessage                  `json:"replyToId"`    // ReplyToId  value comes from MicrosoftBotIncomingChat's "ID".
	Type         string                           `json:"type"`         // Type must be "message".
	Text         string                           `json:"text"`         // Text is the bot's response text.
}

func (hand *HandleMicrosoftBot) Handle(w http.ResponseWriter, r *http.Request) {
	NoCache(w)
	// The payload sent by Microsoft cannot possibly exceed 4 MB
	body, err := misc.ReadAllUpTo(r.Body, 4*1048576)
	if err != nil {
		hand.logger.Warning("HandleMicrosoftBot", "", err, "failed to read incoming chat HTTP request")
		http.Error(w, "failed to read request body", http.StatusBadRequest)
		return
	}
	// Deserialise chat message from incoming request
	var incoming MicrosoftBotIncomingChat
	if err := json.Unmarshal(body, &incoming); err != nil {
		hand.logger.Warning("HandleMicrosoftBot", "", err, "failed to interpret incoming chat request as JSON")
		http.Error(w, "failed to read request body in JSON", http.StatusBadRequest)
		return
	}
	// In the background, process the chat message and formulate a response.
	go func(r *http.Request) {
		convID := incoming.Conversation.ID
		if convID == "" {
			hand.logger.Warning("HandleMicrosoftBot", hand.ClientAppID, nil, "ignore conversation with empty ID")
			return
		}

		// Do not proceed if conversation is too fast
		if !hand.conversationRateLimit.Add(convID, true) {
			return
		}

		// Sometimes a chat app establishes a conversation without any content, just ignore it.
		if incoming.Text == "" {
			return
		}

		latestJWT, err := hand.RetrieveJWT(context.Background())
		if err != nil {
			hand.logger.Warning("HandleMicrosoftBot", incoming.Conversation.ID, err, "cannot reply due to JWT retrieval error")
			return
		}

		// Only process an incoming message if it arrived after server started up
		messageTime, err := time.ParseInLocation("2006-01-02T15:04:05.999999999Z", incoming.Timestamp, time.UTC)
		if err != nil {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "failed to parse timestamp \"%s\" from incoming message", incoming.Timestamp)
			return
		}
		if !messageTime.After(misc.StartupTime.UTC()) {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "ignoring message from \"%s\" that arrived before server started up", incoming.ServiceURL)
			return
		}

		// Process feature command from incoming chat text
		result := hand.cmdProc.Process(context.Background(), toolbox.Command{
			DaemonName: "httpd",
			ClientTag:  convID,
			TimeoutSec: MicrosoftBotCommandTimeoutSec,
			Content:    incoming.Text,
		}, true)

		// Most of the reply properties are directly copied from incoming request
		var reply MicrosoftBotReply
		reply.Conversation = incoming.Conversation
		reply.From = incoming.Recipient
		reply.Locale = incoming.Locale
		reply.Recipient = incoming.From
		reply.ReplyToId = incoming.ID
		reply.Type = "message"
		reply.Text = result.CombinedOutput
		replyBody, err := json.Marshal(reply)
		if err != nil {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "failed to serialise chat reply")
			return
		}
		// Send away the reply
		resp, err := inet.DoHTTP(context.Background(), inet.HTTPRequest{
			Method:      http.MethodPost,
			ContentType: "application/json",
			TimeoutSec:  MicrosoftBotAPITimeoutSec,
			Header:      http.Header{"Authorization": []string{"Bearer " + latestJWT.AccessToken}},
			Body:        bytes.NewReader(replyBody),
		}, incoming.ServiceURL+"/v3/conversations/%s/activities/%s", incoming.Conversation.ID, incoming.ID)
		if err != nil {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "failed to send chat reply due to IO error")
			return
		}
		if err = resp.Non2xxToError(); err != nil {
			hand.logger.Warning("HandleMicrosoftBot", convID, err, "failed to send chat reply due to HTTP error")
			return
		}
	}(r)
}

func (hand *HandleMicrosoftBot) GetRateLimitFactor() int {
	return MicrosoftBotAPIRateLimitFactor
}

func (hand *HandleMicrosoftBot) SelfTest() error {
	if _, err := hand.RetrieveJWT(context.Background()); err != nil {
		return fmt.Errorf("HandleMicrosoftBot encountered error: %v", err)
	}
	return nil
}
<|endoftext|>


<commit_before>// Package aeutils provides some useful utilities for working with
// structs and other objects within the Google App Engine architecture.
package aeutils

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"github.com/mrvdot/golang-utils"

	"appengine"
	"appengine/datastore"
)

// GenerateUniqueSlug generates a slug that's unique within the datastore for this type
// Uses utils.GenerateSlug for initial slug, and appends "-N" where N is an auto-incrementing number
// Until it finds a slug that doesn't already exist for this kind
func GenerateUniqueSlug(ctx appengine.Context, kind string, s string) (slug string) {
	slug = utils.GenerateSlug(s)
	others, err := datastore.NewQuery(kind).
		Filter("Slug = ", slug).
		Count(ctx)
	if err != nil {
		ctx.Errorf("[aeutils/GenerateUniqueSlug] %v", err.Error())
		return ""
	}
	if others == 0 {
		return slug
	}
	counter := 2
	baseSlug := slug
	for others > 0 {
		slug = fmt.Sprintf("%v-%d", baseSlug, counter)
		others, err = datastore.NewQuery(kind).
			Filter("Slug = ", slug).
			Count(ctx)
		if err != nil {
			ctx.Errorf("[aeutils/GenerateUniqueSlug] %v", err.Error())
			return ""
		}
		counter = counter + 1
	}
	return slug
}

// PreSave checks for
// * Method 'BeforeSave' that receives appengine.Context as it's first parameter
//   This can be used for any on save actions that need to be performed (generate a slug, store LastUpdated, or create Key field (see below))
func PreSave(ctx appengine.Context, obj interface{}) error {
	kind, val := reflect.TypeOf(obj), reflect.ValueOf(obj)
	str := val
	if val.Kind() == reflect.Ptr {
		kind, str = kind.Elem(), val.Elem()
	}
	if str.Kind() != reflect.Struct {
		return errors.New(fmt.Sprintf("Must pass a valid object (struct) to aeutils.Save: passed %v", str.Kind()))
	}
	preSave(ctx, val)
	return nil
}

// internal presave method that uses values, so we don't have to check twice
func preSave(ctx appengine.Context, val reflect.Value) {
	if bsMethod := val.MethodByName("BeforeSave"); bsMethod.IsValid() {
		bsMethod.Call([]reflect.Value{reflect.ValueOf(ctx)})
	}
}

// Save takes an appengine.Context and an struct (or pointer to struct) to save in the datastore
// Uses reflection to validate obj is able to be saved. Additionally checks for:
//
// * Field 'Key' of kind *datastore.Key. If exists and has a valid key, uses that for storing in datastore
// 	 ** Important. Due to datastore limitations, this field must not actually be stored in the datastore (ie, needs struct tag `datastore:"-")
// * Field 'ID' of kind int64 to be used as the numeric ID for a datastore key
//	 If key was not retrieved from Key field, ID field is used to create a new key based on that ID
//	 If struct has ID field but no value for it, Save allocates an ID from the datastore and sets it in that field before saving
// * Method 'AfterSave' that receives appengine.Context and *datastore.Key as it's parameters
//   Useful for any post save processing that you might want to do
//
// Finally, ID and Key fields (if they exist) are set with any generated values from Saving obj
func Save(ctx appengine.Context, obj interface{}) (key *datastore.Key, err error) {
	kind, val := reflect.TypeOf(obj), reflect.ValueOf(obj)
	str := val
	if val.Kind() == reflect.Ptr {
		kind, str = kind.Elem(), val.Elem()
	}
	if str.Kind() != reflect.Struct {
		return nil, errors.New(fmt.Sprintf("Must pass a valid object (struct) to aeutils.Save: passed %v", str.Kind()))
	}
	preSave(ctx, val)
	//check for key field first
	keyField := str.FieldByName("Key")
	if keyField.IsValid() {
		keyInterface := keyField.Interface()
		key, _ = keyInterface.(*datastore.Key)
	}
	idField := str.FieldByName("ID")
	dsKind := getDatastoreKind(kind)
	if key == nil {
		if idField.IsValid() && isInt(idField.Kind()) && idField.Int() != 0 {
			key = datastore.NewKey(ctx, dsKind, "", idField.Int(), nil)
		} else {
			newId, _, err := datastore.AllocateIDs(ctx, dsKind, nil, 1)
			if err == nil {
				if idField.IsValid() && isInt(idField.Kind()) {
					idField.SetInt(newId)
				}
				key = datastore.NewKey(ctx, dsKind, "", newId, nil)
			} else {
				key = datastore.NewIncompleteKey(ctx, dsKind, nil)
			}
		}
	}
	key, err = datastore.Put(ctx, key, obj)
	if err != nil {
		ctx.Errorf("[aeutils/Save]: %v", err.Error())
	} else {
		if keyField.IsValid() {
			keyField.Set(reflect.ValueOf(key))
		}
		if idField.IsValid() && isInt(idField.Kind()) {
			idField.SetInt(key.IntID())
		}
		if asMethod := val.MethodByName("AfterSave"); asMethod.IsValid() {
			asMethod.Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(key)})
		}
	}
	return
}

func isInt(kind reflect.Kind) bool {
	switch kind {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return true
	default:
		return false
	}
}

// ExistsInDatastore takes an appengine Context and an interface checks if that interface already exists in datastore
// Will call any 'BeforeSave' method as appropriate, in case that method sets up a 'Key' field, otherwise checks for an ID field
// and assumes that's the datastore IntID
func ExistsInDatastore(ctx appengine.Context, obj interface{}) bool {
	kind, val := reflect.TypeOf(obj), reflect.ValueOf(obj)
	str := val
	if val.Kind().String() == "ptr" {
		kind, str = kind.Elem(), val.Elem()
	}
	if str.Kind().String() != "struct" {
		return false
	}
	dsKind := getDatastoreKind(kind)
	if bsMethod := val.MethodByName("BeforeSave"); bsMethod.IsValid() {
		bsMethod.Call([]reflect.Value{reflect.ValueOf(ctx)})
	}
	var key *datastore.Key
	//check for key field first
	keyField := str.FieldByName("Key")
	if keyField.IsValid() {
		keyInterface := keyField.Interface()
		key, _ = keyInterface.(*datastore.Key)
	}
	idField := str.FieldByName("ID")
	if key == nil {
		if idField.IsValid() && idField.Int() != 0 {
			key = datastore.NewKey(ctx, dsKind, "", idField.Int(), nil)
		}
	}
	if key == nil {
		return false
	}
	err := datastore.Get(ctx, key, obj)
	if err != nil {
		return false
	}
	return true
}

// getDatastoreKind takes a reflect kind and returns a valid string value matching that kind
// Strips off any package namespacing, so 'accounts.Account' becomes just 'Account'
func getDatastoreKind(kind reflect.Type) (dsKind string) {
	dsKind = kind.String()
	if li := strings.LastIndex(dsKind, "."); li >= 0 {
		//Format kind to be in a standard format used for datastore
		dsKind = dsKind[li+1:]
	}
	return
}
<commit_msg>Enable using NDS package<commit_after>// Package aeutils provides some useful utilities for working with
// structs and other objects within the Google App Engine architecture.
package aeutils

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"github.com/mrvdot/golang-utils"
	"github.com/qedus/nds"

	"appengine"
	"appengine/datastore"
)

var (
	// Set to true to use NDS package for Put/Get methods
	UseNDS = false
)

// GenerateUniqueSlug generates a slug that's unique within the datastore for this type
// Uses utils.GenerateSlug for initial slug, and appends "-N" where N is an auto-incrementing number
// Until it finds a slug that doesn't already exist for this kind
func GenerateUniqueSlug(ctx appengine.Context, kind string, s string) (slug string) {
	slug = utils.GenerateSlug(s)
	others, err := datastore.NewQuery(kind).
		Filter("Slug = ", slug).
		Count(ctx)
	if err != nil {
		ctx.Errorf("[aeutils/GenerateUniqueSlug] %v", err.Error())
		return ""
	}
	if others == 0 {
		return slug
	}
	counter := 2
	baseSlug := slug
	for others > 0 {
		slug = fmt.Sprintf("%v-%d", baseSlug, counter)
		others, err = datastore.NewQuery(kind).
			Filter("Slug = ", slug).
			Count(ctx)
		if err != nil {
			ctx.Errorf("[aeutils/GenerateUniqueSlug] %v", err.Error())
			return ""
		}
		counter = counter + 1
	}
	return slug
}

// PreSave checks for
// * Method 'BeforeSave' that receives appengine.Context as it's first parameter
//   This can be used for any on save actions that need to be performed (generate a slug, store LastUpdated, or create Key field (see below))
func PreSave(ctx appengine.Context, obj interface{}) error {
	kind, val := reflect.TypeOf(obj), reflect.ValueOf(obj)
	str := val
	if val.Kind() == reflect.Ptr {
		kind, str = kind.Elem(), val.Elem()
	}
	if str.Kind() != reflect.Struct {
		return errors.New(fmt.Sprintf("Must pass a valid object (struct) to aeutils.Save: passed %v", str.Kind()))
	}
	preSave(ctx, val)
	return nil
}

// internal presave method that uses values, so we don't have to check twice
func preSave(ctx appengine.Context, val reflect.Value) {
	if bsMethod := val.MethodByName("BeforeSave"); bsMethod.IsValid() {
		bsMethod.Call([]reflect.Value{reflect.ValueOf(ctx)})
	}
}

// Save takes an appengine.Context and an struct (or pointer to struct) to save in the datastore
// Uses reflection to validate obj is able to be saved. Additionally checks for:
//
// * Field 'Key' of kind *datastore.Key. If exists and has a valid key, uses that for storing in datastore
// 	 ** Important. Due to datastore limitations, this field must not actually be stored in the datastore (ie, needs struct tag `datastore:"-")
// * Field 'ID' of kind int64 to be used as the numeric ID for a datastore key
//	 If key was not retrieved from Key field, ID field is used to create a new key based on that ID
//	 If struct has ID field but no value for it, Save allocates an ID from the datastore and sets it in that field before saving
// * Method 'AfterSave' that receives appengine.Context and *datastore.Key as it's parameters
//   Useful for any post save processing that you might want to do
//
// Finally, ID and Key fields (if they exist) are set with any generated values from Saving obj
func Save(ctx appengine.Context, obj interface{}) (key *datastore.Key, err error) {
	kind, val := reflect.TypeOf(obj), reflect.ValueOf(obj)
	str := val
	if val.Kind() == reflect.Ptr {
		kind, str = kind.Elem(), val.Elem()
	}
	if str.Kind() != reflect.Struct {
		return nil, errors.New(fmt.Sprintf("Must pass a valid object (struct) to aeutils.Save: passed %v", str.Kind()))
	}
	preSave(ctx, val)
	//check for key field first
	keyField := str.FieldByName("Key")
	if keyField.IsValid() {
		keyInterface := keyField.Interface()
		key, _ = keyInterface.(*datastore.Key)
	}
	idField := str.FieldByName("ID")
	dsKind := getDatastoreKind(kind)
	if key == nil {
		if idField.IsValid() && isInt(idField.Kind()) && idField.Int() != 0 {
			key = datastore.NewKey(ctx, dsKind, "", idField.Int(), nil)
		} else {
			newId, _, err := datastore.AllocateIDs(ctx, dsKind, nil, 1)
			if err == nil {
				if idField.IsValid() && isInt(idField.Kind()) {
					idField.SetInt(newId)
				}
				key = datastore.NewKey(ctx, dsKind, "", newId, nil)
			} else {
				key = datastore.NewIncompleteKey(ctx, dsKind, nil)
			}
		}
	}
	if UseNDS {
		key, err = nds.Put(ctx, key, obj)
	} else {
		key, err = datastore.Put(ctx, key, obj)
	}
	if err != nil {
		ctx.Errorf("[aeutils/Save]: %v", err.Error())
	} else {
		if keyField.IsValid() {
			keyField.Set(reflect.ValueOf(key))
		}
		if idField.IsValid() && isInt(idField.Kind()) {
			idField.SetInt(key.IntID())
		}
		if asMethod := val.MethodByName("AfterSave"); asMethod.IsValid() {
			asMethod.Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(key)})
		}
	}
	return
}

func isInt(kind reflect.Kind) bool {
	switch kind {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return true
	default:
		return false
	}
}

// ExistsInDatastore takes an appengine Context and an interface checks if that interface already exists in datastore
// Will call any 'BeforeSave' method as appropriate, in case that method sets up a 'Key' field, otherwise checks for an ID field
// and assumes that's the datastore IntID
func ExistsInDatastore(ctx appengine.Context, obj interface{}) bool {
	kind, val := reflect.TypeOf(obj), reflect.ValueOf(obj)
	str := val
	if val.Kind().String() == "ptr" {
		kind, str = kind.Elem(), val.Elem()
	}
	if str.Kind().String() != "struct" {
		return false
	}
	dsKind := getDatastoreKind(kind)
	if bsMethod := val.MethodByName("BeforeSave"); bsMethod.IsValid() {
		bsMethod.Call([]reflect.Value{reflect.ValueOf(ctx)})
	}
	var key *datastore.Key
	//check for key field first
	keyField := str.FieldByName("Key")
	if keyField.IsValid() {
		keyInterface := keyField.Interface()
		key, _ = keyInterface.(*datastore.Key)
	}
	idField := str.FieldByName("ID")
	if key == nil {
		if idField.IsValid() && idField.Int() != 0 {
			key = datastore.NewKey(ctx, dsKind, "", idField.Int(), nil)
		}
	}
	if key == nil {
		return false
	}
	var err error
	if UseNDS {
		err = nds.Get(ctx, key, obj)
	} else {
		err = datastore.Get(ctx, key, obj)
	}
	if err != nil {
		return false
	}
	return true
}

// getDatastoreKind takes a reflect kind and returns a valid string value matching that kind
// Strips off any package namespacing, so 'accounts.Account' becomes just 'Account'
func getDatastoreKind(kind reflect.Type) (dsKind string) {
	dsKind = kind.String()
	if li := strings.LastIndex(dsKind, "."); li >= 0 {
		//Format kind to be in a standard format used for datastore
		dsKind = dsKind[li+1:]
	}
	return
}
<|endoftext|>


<commit_before>package basictracer

import (
	"reflect"
	"testing"

	opentracing "github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	"github.com/opentracing/opentracing-go/log"
	"github.com/stretchr/testify/assert"
)

func TestSpan_Baggage(t *testing.T) {
	recorder := NewInMemoryRecorder()
	tracer := NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return true }, // always sample
	})
	span := tracer.StartSpan("x")
	span.SetBaggageItem("x", "y")
	assert.Equal(t, "y", span.BaggageItem("x"))
	span.Finish()
	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, map[string]string{"x": "y"}, spans[0].Context.Baggage)

	recorder.Reset()
	span = tracer.StartSpan("x")
	span.SetBaggageItem("x", "y")
	baggage := make(map[string]string)
	span.Context().ForeachBaggageItem(func(k, v string) bool {
		baggage[k] = v
		return true
	})
	assert.Equal(t, map[string]string{"x": "y"}, baggage)

	span.SetBaggageItem("a", "b")
	baggage = make(map[string]string)
	span.Context().ForeachBaggageItem(func(k, v string) bool {
		baggage[k] = v
		return false // exit early
	})
	assert.Equal(t, 1, len(baggage))
	span.Finish()
	spans = recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, 2, len(spans[0].Context.Baggage))
}

func TestSpan_Sampling(t *testing.T) {
	recorder := NewInMemoryRecorder()
	tracer := NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return true },
	})
	span := tracer.StartSpan("x")
	span.Finish()
	assert.Equal(t, 1, len(recorder.GetSampledSpans()), "by default span should be sampled")

	recorder.Reset()
	span = tracer.StartSpan("x")
	ext.SamplingPriority.Set(span, 0)
	span.Finish()
	assert.Equal(t, 0, len(recorder.GetSampledSpans()), "SamplingPriority=0 should turn off sampling")

	tracer = NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return false },
	})

	recorder.Reset()
	span = tracer.StartSpan("x")
	span.Finish()
	assert.Equal(t, 0, len(recorder.GetSampledSpans()), "by default span should not be sampled")

	recorder.Reset()
	span = tracer.StartSpan("x")
	ext.SamplingPriority.Set(span, 1)
	span.Finish()
	assert.Equal(t, 1, len(recorder.GetSampledSpans()), "SamplingPriority=1 should turn on sampling")
}

func TestSpan_SingleLoggedTaggedSpan(t *testing.T) {
	recorder := NewInMemoryRecorder()
	tracer := NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return true }, // always sample
	})
	span := tracer.StartSpan("x")
	span.LogEventWithPayload("event", "payload")
	span.LogFields(log.String("key_str", "value"), log.Uint32("32bit", 4294967295))
	span.SetTag("tag", "value")
	span.Finish()
	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, "x", spans[0].Operation)
	assert.Equal(t, 2, len(spans[0].Logs))
	assert.Equal(t, opentracing.Tags{"tag": "value"}, spans[0].Tags)
	lfv := log.NewLogFieldValidator(t, spans[0].Logs[0].Fields)
	lfv.
		ExpectNextFieldEquals("event", reflect.String, "event").
		ExpectNextFieldEquals("payload", reflect.Interface, "payload")
	lfv = log.NewLogFieldValidator(t, spans[0].Logs[1].Fields)
	lfv.
		ExpectNextFieldEquals("key_str", reflect.String, "value").
		ExpectNextFieldEquals("32bit", reflect.Uint32, "4294967295")
}

func TestSpan_TrimUnsampledSpans(t *testing.T) {
	recorder := NewInMemoryRecorder()
	// Tracer that trims only unsampled but always samples
	tracer := NewWithOptions(Options{
		Recorder:           recorder,
		ShouldSample:       func(traceID uint64) bool { return true }, // always sample
		TrimUnsampledSpans: true,
	})

	span := tracer.StartSpan("x")
	span.LogFields(log.String("key_str", "value"), log.Uint32("32bit", 4294967295))
	span.SetTag("tag", "value")
	span.Finish()
	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, 1, len(spans[0].Logs))
	assert.Equal(t, opentracing.Tags{"tag": "value"}, spans[0].Tags)
	lfv := log.NewLogFieldValidator(t, spans[0].Logs[0].Fields)
	lfv.
		ExpectNextFieldEquals("key_str", reflect.String, "value").
		ExpectNextFieldEquals("32bit", reflect.Uint32, "4294967295")

	recorder.Reset()
	// Tracer that trims only unsampled and never samples
	tracer = NewWithOptions(Options{
		Recorder:           recorder,
		ShouldSample:       func(traceID uint64) bool { return false }, // never sample
		TrimUnsampledSpans: true,
	})

	span = tracer.StartSpan("x")
	span.LogFields(log.String("key_str", "value"), log.Uint32("32bit", 4294967295))
	span.SetTag("tag", "value")
	span.Finish()
	spans = recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, 0, len(spans[0].Logs))
	assert.Equal(t, 0, len(spans[0].Tags))
}

func TestSpan_DropAllLogs(t *testing.T) {
	recorder := NewInMemoryRecorder()
	// Tracer that drops logs
	tracer := NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return true }, // always sample
		DropAllLogs:  true,
	})

	span := tracer.StartSpan("x")
	span.LogFields(log.String("key_str", "value"), log.Uint32("32bit", 4294967295))
	span.SetTag("tag", "value")
	span.Finish()
	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, "x", spans[0].Operation)
	assert.Equal(t, opentracing.Tags{"tag": "value"}, spans[0].Tags)
	// Only logs are dropped
	assert.Equal(t, 0, len(spans[0].Logs))
}
<commit_msg>Adjust to delinted names<commit_after>package basictracer

import (
	"reflect"
	"testing"

	opentracing "github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	"github.com/opentracing/opentracing-go/log"
	"github.com/stretchr/testify/assert"
)

func TestSpan_Baggage(t *testing.T) {
	recorder := NewInMemoryRecorder()
	tracer := NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return true }, // always sample
	})
	span := tracer.StartSpan("x")
	span.SetBaggageItem("x", "y")
	assert.Equal(t, "y", span.BaggageItem("x"))
	span.Finish()
	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, map[string]string{"x": "y"}, spans[0].Context.Baggage)

	recorder.Reset()
	span = tracer.StartSpan("x")
	span.SetBaggageItem("x", "y")
	baggage := make(map[string]string)
	span.Context().ForeachBaggageItem(func(k, v string) bool {
		baggage[k] = v
		return true
	})
	assert.Equal(t, map[string]string{"x": "y"}, baggage)

	span.SetBaggageItem("a", "b")
	baggage = make(map[string]string)
	span.Context().ForeachBaggageItem(func(k, v string) bool {
		baggage[k] = v
		return false // exit early
	})
	assert.Equal(t, 1, len(baggage))
	span.Finish()
	spans = recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, 2, len(spans[0].Context.Baggage))
}

func TestSpan_Sampling(t *testing.T) {
	recorder := NewInMemoryRecorder()
	tracer := NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return true },
	})
	span := tracer.StartSpan("x")
	span.Finish()
	assert.Equal(t, 1, len(recorder.GetSampledSpans()), "by default span should be sampled")

	recorder.Reset()
	span = tracer.StartSpan("x")
	ext.SamplingPriority.Set(span, 0)
	span.Finish()
	assert.Equal(t, 0, len(recorder.GetSampledSpans()), "SamplingPriority=0 should turn off sampling")

	tracer = NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return false },
	})

	recorder.Reset()
	span = tracer.StartSpan("x")
	span.Finish()
	assert.Equal(t, 0, len(recorder.GetSampledSpans()), "by default span should not be sampled")

	recorder.Reset()
	span = tracer.StartSpan("x")
	ext.SamplingPriority.Set(span, 1)
	span.Finish()
	assert.Equal(t, 1, len(recorder.GetSampledSpans()), "SamplingPriority=1 should turn on sampling")
}

func TestSpan_SingleLoggedTaggedSpan(t *testing.T) {
	recorder := NewInMemoryRecorder()
	tracer := NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return true }, // always sample
	})
	span := tracer.StartSpan("x")
	span.LogEventWithPayload("event", "payload")
	span.LogFields(log.String("key_str", "value"), log.Uint32("32bit", 4294967295))
	span.SetTag("tag", "value")
	span.Finish()
	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, "x", spans[0].Operation)
	assert.Equal(t, 2, len(spans[0].Logs))
	assert.Equal(t, opentracing.Tags{"tag": "value"}, spans[0].Tags)
	fv := log.NewFieldValidator(t, spans[0].Logs[0].Fields)
	fv.
		ExpectNextFieldEquals("event", reflect.String, "event").
		ExpectNextFieldEquals("payload", reflect.Interface, "payload")
	fv = log.NewFieldValidator(t, spans[0].Logs[1].Fields)
	fv.
		ExpectNextFieldEquals("key_str", reflect.String, "value").
		ExpectNextFieldEquals("32bit", reflect.Uint32, "4294967295")
}

func TestSpan_TrimUnsampledSpans(t *testing.T) {
	recorder := NewInMemoryRecorder()
	// Tracer that trims only unsampled but always samples
	tracer := NewWithOptions(Options{
		Recorder:           recorder,
		ShouldSample:       func(traceID uint64) bool { return true }, // always sample
		TrimUnsampledSpans: true,
	})

	span := tracer.StartSpan("x")
	span.LogFields(log.String("key_str", "value"), log.Uint32("32bit", 4294967295))
	span.SetTag("tag", "value")
	span.Finish()
	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, 1, len(spans[0].Logs))
	assert.Equal(t, opentracing.Tags{"tag": "value"}, spans[0].Tags)
	fv := log.NewFieldValidator(t, spans[0].Logs[0].Fields)
	fv.
		ExpectNextFieldEquals("key_str", reflect.String, "value").
		ExpectNextFieldEquals("32bit", reflect.Uint32, "4294967295")

	recorder.Reset()
	// Tracer that trims only unsampled and never samples
	tracer = NewWithOptions(Options{
		Recorder:           recorder,
		ShouldSample:       func(traceID uint64) bool { return false }, // never sample
		TrimUnsampledSpans: true,
	})

	span = tracer.StartSpan("x")
	span.LogFields(log.String("key_str", "value"), log.Uint32("32bit", 4294967295))
	span.SetTag("tag", "value")
	span.Finish()
	spans = recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, 0, len(spans[0].Logs))
	assert.Equal(t, 0, len(spans[0].Tags))
}

func TestSpan_DropAllLogs(t *testing.T) {
	recorder := NewInMemoryRecorder()
	// Tracer that drops logs
	tracer := NewWithOptions(Options{
		Recorder:     recorder,
		ShouldSample: func(traceID uint64) bool { return true }, // always sample
		DropAllLogs:  true,
	})

	span := tracer.StartSpan("x")
	span.LogFields(log.String("key_str", "value"), log.Uint32("32bit", 4294967295))
	span.SetTag("tag", "value")
	span.Finish()
	spans := recorder.GetSpans()
	assert.Equal(t, 1, len(spans))
	assert.Equal(t, "x", spans[0].Operation)
	assert.Equal(t, opentracing.Tags{"tag": "value"}, spans[0].Tags)
	// Only logs are dropped
	assert.Equal(t, 0, len(spans[0].Logs))
}
<|endoftext|>


<commit_before>package spec

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/summerwind/h2spec/config"
	"github.com/summerwind/h2spec/log"
)

var (
	// ErrTimeout is used when the test times out.
	ErrTimeout = errors.New("Timeout")
	// ErrSkipped is used when the test skipped.
	ErrSkipped = errors.New("Skipped")
)

// TestGroup represents a group of test case.
type TestGroup struct {
	Key         string
	Section     string
	Name        string
	Strict      bool
	Parent      *TestGroup
	Groups      []*TestGroup
	Tests       []*TestCase
	StrictTests []*TestCase

	PassedCount  int
	FailedCount  int
	SkippedCount int
}

// IsRoot returns bool as to whether it is the parent of all groups.
func (tg *TestGroup) IsRoot() bool {
	return tg.Parent == nil
}

// ID returns the unique ID of this group.
func (tg *TestGroup) ID() string {
	if tg.IsRoot() {
		return tg.Key
	}

	return fmt.Sprintf("%s/%s", tg.Key, tg.Section)
}

// Title returns the title of this group.
func (tg *TestGroup) Title() string {
	if tg.IsRoot() {
		return fmt.Sprintf("%s", tg.Name)
	} else {
		return fmt.Sprintf("%s. %s", tg.Section, tg.Name)
	}
}

// Level returns a number. Level is determined by Key and the number
// of "." included in Section.
func (tg *TestGroup) Level() int {
	if tg.IsRoot() {
		return 0
	}

	return strings.Count(tg.Section, ".") + 1
}

// Test runs all the tests included in this group.
func (tg *TestGroup) Test(c *config.Config) {
	level := tg.Level()

	if tg.Strict && !c.Strict {
		return
	}

	mode := c.RunMode(tg.ID())
	if mode == config.RunModeNone {
		return
	}

	log.SetIndentLevel(level)
	log.Println(tg.Title())
	log.SetIndentLevel(level + 1)

	tests := append(tg.Tests, tg.StrictTests...)
	tested := false

	for i, tc := range tests {
		seq := i + 1

		err := tc.Test(c, seq)
		if err != nil {
			fmt.Printf("\nError: %v\n", err)
			os.Exit(1)
		}

		if tc.Result != nil {
			if tc.Result.Failed {
				tg.FailedCount += 1
			} else if tc.Result.Skipped {
				tg.SkippedCount += 1
			} else {
				tg.PassedCount += 1
			}

			tested = true
		}
	}

	if tested {
		log.PrintBlankLine()
	}

	for _, g := range tg.Groups {
		g.Test(c)
		tg.FailedCount += g.FailedCount
		tg.SkippedCount += g.SkippedCount
		tg.PassedCount += g.PassedCount
	}
}

// AddTestGroup registers a group to this group.
func (tg *TestGroup) AddTestGroup(stg *TestGroup) {
	stg.Parent = tg
	stg.Strict = tg.Strict
	tg.Groups = append(tg.Groups, stg)
}

// AddTestGroup registers a test to this group.
func (tg *TestGroup) AddTestCase(tc *TestCase) {
	tc.Parent = tg
	if tg.Strict {
		tc.Strict = true
		tg.StrictTests = append(tg.StrictTests, tc)
	} else {
		tg.Tests = append(tg.Tests, tc)
	}
}

// TestCase represents a test case.
type TestCase struct {
	Desc        string
	Requirement string
	Strict      bool
	Parent      *TestGroup
	Result      *TestResult
	Run         func(c *config.Config, conn *Conn) error
}

// Test runs itself as a test case.
func (tc *TestCase) Test(c *config.Config, seq int) error {
	if c.DryRun {
		msg := fmt.Sprintf("%s %s", seqStr(seq), tc.Desc)
		log.Println(msg)
		return nil
	}

	if tc.Strict && !c.Strict {
		return nil
	}

	mode := c.RunMode(fmt.Sprintf("%s/%d", tc.Parent.ID(), seq))
	if mode == config.RunModeNone {
		return nil
	}

	if !c.Verbose {
		msg := gray(fmt.Sprintf("  %s %s", seqStr(seq), tc.Desc))
		log.Print(msg)
	}

	conn, err := Dial(c)
	if err != nil {
		msg := red(fmt.Sprintf("%s %s %s", "×", seqStr(seq), tc.Desc))
		log.ResetLine()
		log.Println(msg)
		return err
	}
	defer conn.Close()

	start := time.Now()
	err = tc.Run(c, conn)
	end := time.Now()

	log.ResetLine()

	tr := NewTestResult(tc, seq, err, end.Sub(start))
	tr.Print()
	tc.Result = tr

	return nil
}

// TestError represents a error result of test case and implements
// type error.
type TestError struct {
	Expected []string
	Actual   string
}

// Returns a string containing the reason of the error.
func (e TestError) Error() string {
	return fmt.Sprintf("%s\n%s", strings.Join(e.Expected, "\n"), e.Actual)
}

// TestResult represents a result of test case.
type TestResult struct {
	TestCase *TestCase
	Sequence int
	Error    error
	Duration time.Duration

	Skipped bool
	Failed  bool
}

// NewTestResult returns a TestResult.
func NewTestResult(tc *TestCase, seq int, err error, d time.Duration) *TestResult {
	skipped := false
	failed := false

	if err != nil {
		if err == ErrSkipped {
			skipped = true
		} else {
			failed = true
		}
	}

	tr := TestResult{
		TestCase: tc,
		Sequence: seq,
		Error:    err,
		Duration: d,
		Skipped:  skipped,
		Failed:   failed,
	}

	return &tr
}

// Print prints the result of test case.
func (tr *TestResult) Print() {
	tc := tr.TestCase
	desc := tc.Desc
	seq := seqStr(tr.Sequence)

	if tr.Skipped {
		log.Println(cyan(fmt.Sprintf("%s %s", seq, desc)))
		return
	}

	if !tr.Failed {
		log.Println(fmt.Sprintf("%s %s %s", green("✔"), gray(seq), gray(desc)))
		return
	}

	log.Println(red(fmt.Sprintf("%s %s %s", "×", seq, desc)))
	err, ok := tr.Error.(*TestError)
	if ok {
		level := log.IndentLevel
		log.SetIndentLevel(level + 1)
		defer func() {
			log.SetIndentLevel(level)
		}()

		log.Println(red(fmt.Sprintf("-> %s", tc.Requirement)))
		label := "Expected: "
		for i, ex := range err.Expected {
			if i != 0 {
				label = strings.Repeat(" ", len(label))
			}
			log.Println(yellow(fmt.Sprintf("   %s%s", label, ex)))
		}
		log.Println(green(fmt.Sprintf("     Actual: %s", err.Actual)))

		return
	}
	if err == nil {
		log.Println(red(fmt.Sprintf("Error: %v", tr.Error.Error())))
	} else {
		log.Println(red(fmt.Sprintf("Error: %v", err)))
	}
}

func seqStr(seq int) string {
	return fmt.Sprintf("%d:", seq)
}
<commit_msg>Handle strict flag of test group properly<commit_after>package spec

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/summerwind/h2spec/config"
	"github.com/summerwind/h2spec/log"
)

var (
	// ErrTimeout is used when the test times out.
	ErrTimeout = errors.New("Timeout")
	// ErrSkipped is used when the test skipped.
	ErrSkipped = errors.New("Skipped")
)

// TestGroup represents a group of test case.
type TestGroup struct {
	Key         string
	Section     string
	Name        string
	Strict      bool
	Parent      *TestGroup
	Groups      []*TestGroup
	Tests       []*TestCase
	StrictTests []*TestCase

	PassedCount  int
	FailedCount  int
	SkippedCount int
}

// IsRoot returns bool as to whether it is the parent of all groups.
func (tg *TestGroup) IsRoot() bool {
	return tg.Parent == nil
}

// ID returns the unique ID of this group.
func (tg *TestGroup) ID() string {
	if tg.IsRoot() {
		return tg.Key
	}

	return fmt.Sprintf("%s/%s", tg.Key, tg.Section)
}

// Title returns the title of this group.
func (tg *TestGroup) Title() string {
	if tg.IsRoot() {
		return fmt.Sprintf("%s", tg.Name)
	} else {
		return fmt.Sprintf("%s. %s", tg.Section, tg.Name)
	}
}

// Level returns a number. Level is determined by Key and the number
// of "." included in Section.
func (tg *TestGroup) Level() int {
	if tg.IsRoot() {
		return 0
	}

	return strings.Count(tg.Section, ".") + 1
}

// Test runs all the tests included in this group.
func (tg *TestGroup) Test(c *config.Config) {
	level := tg.Level()

	if tg.Strict && !c.Strict {
		return
	}

	mode := c.RunMode(tg.ID())
	if mode == config.RunModeNone {
		return
	}

	log.SetIndentLevel(level)
	log.Println(tg.Title())
	log.SetIndentLevel(level + 1)

	tests := append(tg.Tests, tg.StrictTests...)
	tested := false

	for i, tc := range tests {
		seq := i + 1

		err := tc.Test(c, seq)
		if err != nil {
			fmt.Printf("\nError: %v\n", err)
			os.Exit(1)
		}

		if tc.Result != nil {
			if tc.Result.Failed {
				tg.FailedCount += 1
			} else if tc.Result.Skipped {
				tg.SkippedCount += 1
			} else {
				tg.PassedCount += 1
			}

			tested = true
		}
	}

	if tested {
		log.PrintBlankLine()
	}

	for _, g := range tg.Groups {
		g.Test(c)
		tg.FailedCount += g.FailedCount
		tg.SkippedCount += g.SkippedCount
		tg.PassedCount += g.PassedCount
	}
}

// AddTestGroup registers a group to this group.
func (tg *TestGroup) AddTestGroup(stg *TestGroup) {
	stg.Parent = tg
	if tg.Strict {
		stg.Strict = true
	}
	tg.Groups = append(tg.Groups, stg)
}

// AddTestGroup registers a test to this group.
func (tg *TestGroup) AddTestCase(tc *TestCase) {
	tc.Parent = tg
	if tg.Strict {
		tc.Strict = true
		tg.StrictTests = append(tg.StrictTests, tc)
	} else {
		tg.Tests = append(tg.Tests, tc)
	}
}

// TestCase represents a test case.
type TestCase struct {
	Desc        string
	Requirement string
	Strict      bool
	Parent      *TestGroup
	Result      *TestResult
	Run         func(c *config.Config, conn *Conn) error
}

// Test runs itself as a test case.
func (tc *TestCase) Test(c *config.Config, seq int) error {
	if c.DryRun {
		msg := fmt.Sprintf("%s %s", seqStr(seq), tc.Desc)
		log.Println(msg)
		return nil
	}

	if tc.Strict && !c.Strict {
		return nil
	}

	mode := c.RunMode(fmt.Sprintf("%s/%d", tc.Parent.ID(), seq))
	if mode == config.RunModeNone {
		return nil
	}

	if !c.Verbose {
		msg := gray(fmt.Sprintf("  %s %s", seqStr(seq), tc.Desc))
		log.Print(msg)
	}

	conn, err := Dial(c)
	if err != nil {
		msg := red(fmt.Sprintf("%s %s %s", "×", seqStr(seq), tc.Desc))
		log.ResetLine()
		log.Println(msg)
		return err
	}
	defer conn.Close()

	start := time.Now()
	err = tc.Run(c, conn)
	end := time.Now()

	log.ResetLine()

	tr := NewTestResult(tc, seq, err, end.Sub(start))
	tr.Print()
	tc.Result = tr

	return nil
}

// TestError represents a error result of test case and implements
// type error.
type TestError struct {
	Expected []string
	Actual   string
}

// Returns a string containing the reason of the error.
func (e TestError) Error() string {
	return fmt.Sprintf("%s\n%s", strings.Join(e.Expected, "\n"), e.Actual)
}

// TestResult represents a result of test case.
type TestResult struct {
	TestCase *TestCase
	Sequence int
	Error    error
	Duration time.Duration

	Skipped bool
	Failed  bool
}

// NewTestResult returns a TestResult.
func NewTestResult(tc *TestCase, seq int, err error, d time.Duration) *TestResult {
	skipped := false
	failed := false

	if err != nil {
		if err == ErrSkipped {
			skipped = true
		} else {
			failed = true
		}
	}

	tr := TestResult{
		TestCase: tc,
		Sequence: seq,
		Error:    err,
		Duration: d,
		Skipped:  skipped,
		Failed:   failed,
	}

	return &tr
}

// Print prints the result of test case.
func (tr *TestResult) Print() {
	tc := tr.TestCase
	desc := tc.Desc
	seq := seqStr(tr.Sequence)

	if tr.Skipped {
		log.Println(cyan(fmt.Sprintf("%s %s", seq, desc)))
		return
	}

	if !tr.Failed {
		log.Println(fmt.Sprintf("%s %s %s", green("✔"), gray(seq), gray(desc)))
		return
	}

	log.Println(red(fmt.Sprintf("%s %s %s", "×", seq, desc)))
	err, ok := tr.Error.(*TestError)
	if ok {
		level := log.IndentLevel
		log.SetIndentLevel(level + 1)
		defer func() {
			log.SetIndentLevel(level)
		}()

		log.Println(red(fmt.Sprintf("-> %s", tc.Requirement)))
		label := "Expected: "
		for i, ex := range err.Expected {
			if i != 0 {
				label = strings.Repeat(" ", len(label))
			}
			log.Println(yellow(fmt.Sprintf("   %s%s", label, ex)))
		}
		log.Println(green(fmt.Sprintf("     Actual: %s", err.Actual)))

		return
	}
	if err == nil {
		log.Println(red(fmt.Sprintf("Error: %v", tr.Error.Error())))
	} else {
		log.Println(red(fmt.Sprintf("Error: %v", err)))
	}
}

func seqStr(seq int) string {
	return fmt.Sprintf("%d:", seq)
}
<|endoftext|>


<commit_before>package sdl

/*
#include "sdl_wrapper.h"

#if (SDL_VERSION_ATLEAST(2,0,6))
#if defined(_WIN32)
	#include <SDL2/SDL_vulkan.h>
#else
	#include <SDL_vulkan.h>
#endif
#else //!SDL_VERSION_ATLEAST(2,0,6)

#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;
#else
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
#endif

VK_DEFINE_HANDLE(VkInstance)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR)
typedef VkInstance SDL_vulkanInstance;
typedef VkSurfaceKHR SDL_vulkanSurface;

#pragma message("SDL_Vulkan_LoadLibrary is not supported before SDL 2.0.6")
static int SDL_Vulkan_LoadLibrary(const char *path)
{
	return 0;
}

#pragma message("SDL_Vulkan_GetVkGetInstanceProcAddr is not supported before SDL 2.0.6")
static void* SDL_Vulkan_GetVkGetInstanceProcAddr(void)
{
	return NULL;
}

#pragma message("SDL_Vulkan_UnloadLibrary is not supported before SDL 2.0.6")
static void SDL_Vulkan_UnloadLibrary(void) {}

#pragma message("SDL_Vulkan_GetInstanceExtensions is not supported before SDL 2.0.6")
static int SDL_Vulkan_GetInstanceExtensions(SDL_Window *window, unsigned int *pCount, const char **pNames)
{
	return 0;
}

#pragma message("SDL_Vulkan_CreateSurface is not supported before SDL 2.0.6")
static int SDL_Vulkan_CreateSurface(SDL_Window *window, VkInstance instance, VkSurfaceKHR *surface)
{
	return 0;
}

#pragma message("SDL_Vulkan_GetDrawableSize is not supported before SDL 2.0.6")
static void SDL_Vulkan_GetDrawableSize(SDL_Window *window, int *w, int *h) {
	*w = 0;
	*h = 0;
}

#endif
*/
import "C"
import (
	"errors"
	"reflect"
	"unsafe"
)

// VulkanLoadLibrary dynamically loads a Vulkan loader library.
func VulkanLoadLibrary(path string) error {
	var ret C.int
	if path == "" {
		ret = C.SDL_Vulkan_LoadLibrary(nil)
	} else {
		cpath := C.CString(path)
		defer C.free(unsafe.Pointer(cpath))
		ret = C.SDL_Vulkan_LoadLibrary(cpath)
	}
	if int(ret) == -1 {
		return GetError()
	}
	return nil
}

// VulkanGetVkGetInstanceProcAddr gets the address of the vkGetInstanceProcAddr function.
func VulkanGetVkGetInstanceProcAddr() unsafe.Pointer {
	return C.SDL_Vulkan_GetVkGetInstanceProcAddr()
}

// VulkanUnloadLibrary unloads the Vulkan loader library previously loaded by VulkanLoadLibrary().
func VulkanUnloadLibrary() {
	C.SDL_Vulkan_UnloadLibrary()
}

// VulkanGetInstanceExtensions gets the names of the Vulkan instance extensions needed to create a surface with VulkanCreateSurface().
func (window *Window) VulkanGetInstanceExtensions() []string {
	var count C.uint
	C.SDL_Vulkan_GetInstanceExtensions(window.cptr(), &count, nil)
	if count == 0 {
		return nil
	}

	strptrs := make([]*C.char, uint(count))
	C.SDL_Vulkan_GetInstanceExtensions(window.cptr(), &count, &strptrs[0])
	extensions := make([]string, uint(count))
	for i := range strptrs {
		extensions[i] = C.GoString(strptrs[i])
	}
	return extensions
}

// VulkanCreateSurface creates a Vulkan rendering surface for a window.
func (window *Window) VulkanCreateSurface(instance interface{}) (surface uintptr, err error) {
	if instance == nil {
		return 0, errors.New("vulkan: instance is nil")
	}
	val := reflect.ValueOf(instance)
	if val.Kind() != reflect.Ptr {
		return 0, errors.New("vulkan: instance is not a VkInstance (expected kind Ptr, got " + val.Kind().String() + ")")
	}
	var vulkanSurface C.VkSurfaceKHR
	if C.SDL_Vulkan_CreateSurface(window.cptr(),
		(C.VkInstance)(unsafe.Pointer(val.Pointer())),
		(*C.VkSurfaceKHR)(unsafe.Pointer(&vulkanSurface))) == C.SDL_FALSE {

		return 0, GetError()
	}
	return uintptr(unsafe.Pointer(&vulkanSurface)), nil
}

// VulkanGetDrawableSize gets the size of a window's underlying drawable in pixels (for use with setting viewport, scissor & etc).
func (window *Window) VulkanGetDrawableSize() (w, h int32) {
	var _w, _h C.int
	C.SDL_Vulkan_GetDrawableSize(window.cptr(), &_w, &_h)
	return int32(_w), int32(_h)
}
<commit_msg>sdl/vulkan: add links to documentation (#361)<commit_after>package sdl

/*
#include "sdl_wrapper.h"

#if (SDL_VERSION_ATLEAST(2,0,6))
#if defined(_WIN32)
	#include <SDL2/SDL_vulkan.h>
#else
	#include <SDL_vulkan.h>
#endif
#else //!SDL_VERSION_ATLEAST(2,0,6)

#define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
#if defined(__LP64__) || defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;
#else
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
#endif

VK_DEFINE_HANDLE(VkInstance)
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkSurfaceKHR)
typedef VkInstance SDL_vulkanInstance;
typedef VkSurfaceKHR SDL_vulkanSurface;

#pragma message("SDL_Vulkan_LoadLibrary is not supported before SDL 2.0.6")
static int SDL_Vulkan_LoadLibrary(const char *path)
{
	return 0;
}

#pragma message("SDL_Vulkan_GetVkGetInstanceProcAddr is not supported before SDL 2.0.6")
static void* SDL_Vulkan_GetVkGetInstanceProcAddr(void)
{
	return NULL;
}

#pragma message("SDL_Vulkan_UnloadLibrary is not supported before SDL 2.0.6")
static void SDL_Vulkan_UnloadLibrary(void) {}

#pragma message("SDL_Vulkan_GetInstanceExtensions is not supported before SDL 2.0.6")
static int SDL_Vulkan_GetInstanceExtensions(SDL_Window *window, unsigned int *pCount, const char **pNames)
{
	return 0;
}

#pragma message("SDL_Vulkan_CreateSurface is not supported before SDL 2.0.6")
static int SDL_Vulkan_CreateSurface(SDL_Window *window, VkInstance instance, VkSurfaceKHR *surface)
{
	return 0;
}

#pragma message("SDL_Vulkan_GetDrawableSize is not supported before SDL 2.0.6")
static void SDL_Vulkan_GetDrawableSize(SDL_Window *window, int *w, int *h) {
	*w = 0;
	*h = 0;
}

#endif
*/
import "C"
import (
	"errors"
	"reflect"
	"unsafe"
)

// VulkanLoadLibrary dynamically loads a Vulkan loader library.
// (https://wiki.libsdl.org/SDL_Vulkan_LoadLibrary)
func VulkanLoadLibrary(path string) error {
	var ret C.int
	if path == "" {
		ret = C.SDL_Vulkan_LoadLibrary(nil)
	} else {
		cpath := C.CString(path)
		defer C.free(unsafe.Pointer(cpath))
		ret = C.SDL_Vulkan_LoadLibrary(cpath)
	}
	if int(ret) == -1 {
		return GetError()
	}
	return nil
}

// VulkanGetVkGetInstanceProcAddr gets the address of the vkGetInstanceProcAddr function.
// (https://wiki.libsdl.org/SDL_Vulkan_GetVkInstanceProcAddr)
func VulkanGetVkGetInstanceProcAddr() unsafe.Pointer {
	return C.SDL_Vulkan_GetVkGetInstanceProcAddr()
}

// VulkanUnloadLibrary unloads the Vulkan loader library previously loaded by VulkanLoadLibrary().
// (https://wiki.libsdl.org/SDL_Vulkan_UnloadLibrary)
func VulkanUnloadLibrary() {
	C.SDL_Vulkan_UnloadLibrary()
}

// VulkanGetInstanceExtensions gets the names of the Vulkan instance extensions needed to create a surface with VulkanCreateSurface().
// (https://wiki.libsdl.org/SDL_Vulkan_GetInstanceExtensions)
func (window *Window) VulkanGetInstanceExtensions() []string {
	var count C.uint
	C.SDL_Vulkan_GetInstanceExtensions(window.cptr(), &count, nil)
	if count == 0 {
		return nil
	}

	strptrs := make([]*C.char, uint(count))
	C.SDL_Vulkan_GetInstanceExtensions(window.cptr(), &count, &strptrs[0])
	extensions := make([]string, uint(count))
	for i := range strptrs {
		extensions[i] = C.GoString(strptrs[i])
	}
	return extensions
}

// VulkanCreateSurface creates a Vulkan rendering surface for a window.
// (https://wiki.libsdl.org/SDL_Vulkan_CreateSurface)
func (window *Window) VulkanCreateSurface(instance interface{}) (surface uintptr, err error) {
	if instance == nil {
		return 0, errors.New("vulkan: instance is nil")
	}
	val := reflect.ValueOf(instance)
	if val.Kind() != reflect.Ptr {
		return 0, errors.New("vulkan: instance is not a VkInstance (expected kind Ptr, got " + val.Kind().String() + ")")
	}
	var vulkanSurface C.VkSurfaceKHR
	if C.SDL_Vulkan_CreateSurface(window.cptr(),
		(C.VkInstance)(unsafe.Pointer(val.Pointer())),
		(*C.VkSurfaceKHR)(unsafe.Pointer(&vulkanSurface))) == C.SDL_FALSE {

		return 0, GetError()
	}
	return uintptr(unsafe.Pointer(&vulkanSurface)), nil
}

// VulkanGetDrawableSize gets the size of a window's underlying drawable in pixels (for use with setting viewport, scissor & etc).
// (https://wiki.libsdl.org/SDL_Vulkan_GetDrawableSize)
func (window *Window) VulkanGetDrawableSize() (w, h int32) {
	var _w, _h C.int
	C.SDL_Vulkan_GetDrawableSize(window.cptr(), &_w, &_h)
	return int32(_w), int32(_h)
}
<|endoftext|>