<commit_before>/*
 * mountpoint.go - Contains all the functionality for finding mountpoints and
 * using UUIDs to refer to them. Specifically, we can find the mountpoint of a
 * path, get info about a mountpoint, and find mountpoints with a specific UUID.
 *
 * Copyright 2017 Google Inc.
 * Author: Joe Richey (joerichey@google.com)
 *
 * 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 filesystem

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"

	"github.com/pkg/errors"
)

var (
	// These maps hold data about the state of the system's mountpoints.
	mountsByPath   map[string]*Mount
	mountsByDevice map[string][]*Mount
	// Used to make the mount functions thread safe
	mountMutex sync.Mutex
	// True if the maps have been successfully initialized.
	mountsInitialized bool
	// Supported tokens for filesystem links
	uuidToken = "UUID"
	// Location to perform UUID lookup
	uuidDirectory = "/dev/disk/by-uuid"
)

// Unescape octal-encoded escape sequences in a string from the mountinfo file.
// The kernel encodes the ' ', '\t', '\n', and '\\' bytes this way.  This
// function exactly inverts what the kernel does, including by preserving
// invalid UTF-8.
func unescapeString(str string) string {
	var sb strings.Builder
	for i := 0; i < len(str); i++ {
		b := str[i]
		if b == '\\' && i+3 < len(str) {
			if parsed, err := strconv.ParseInt(str[i+1:i+4], 8, 8); err == nil {
				b = uint8(parsed)
				i += 3
			}
		}
		sb.WriteByte(b)
	}
	return sb.String()
}

// We get the device name via the device number rather than use the mount source
// field directly.  This is necessary to handle a rootfs that was mounted via
// the kernel command line, since mountinfo always shows /dev/root for that.
// This assumes that the device nodes are in the standard location.
func getDeviceName(num DeviceNumber) string {
	linkPath := fmt.Sprintf("/sys/dev/block/%v", num)
	if target, err := os.Readlink(linkPath); err == nil {
		return fmt.Sprintf("/dev/%s", filepath.Base(target))
	}
	return ""
}

// Parse one line of /proc/self/mountinfo.
//
// The line contains the following space-separated fields:
//	[0] mount ID
//	[1] parent ID
//	[2] major:minor
//	[3] root
//	[4] mount point
//	[5] mount options
//	[6...n-1] optional field(s)
//	[n] separator
//	[n+1] filesystem type
//	[n+2] mount source
//	[n+3] super options
//
// For more details, see https://www.kernel.org/doc/Documentation/filesystems/proc.txt
func parseMountInfoLine(line string) *Mount {
	fields := strings.Split(line, " ")
	if len(fields) < 10 {
		return nil
	}

	// Count the optional fields.  In case new fields are appended later,
	// don't simply assume that n == len(fields) - 4.
	n := 6
	for fields[n] != "-" {
		n++
		if n >= len(fields) {
			return nil
		}
	}
	if n+3 >= len(fields) {
		return nil
	}

	var mnt *Mount = &Mount{}
	var err error
	mnt.DeviceNumber, err = newDeviceNumberFromString(fields[2])
	if err != nil {
		return nil
	}
	mnt.Path = unescapeString(fields[4])
	mnt.FilesystemType = unescapeString(fields[n+1])
	mnt.Device = getDeviceName(mnt.DeviceNumber)
	return mnt
}

// loadMountInfo populates the Mount mappings by parsing /proc/self/mountinfo.
// It returns an error if the Mount mappings cannot be populated.
func loadMountInfo() error {
	if mountsInitialized {
		return nil
	}
	mountsByPath = make(map[string]*Mount)
	mountsByDevice = make(map[string][]*Mount)

	file, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return err
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		mnt := parseMountInfoLine(line)
		if mnt == nil {
			log.Printf("ignoring invalid mountinfo line %q", line)
			continue
		}

		// We can only use mountpoints that are directories for fscrypt.
		if !isDir(mnt.Path) {
			log.Printf("ignoring mountpoint %q because it is not a directory", mnt.Path)
			continue
		}

		// Note this overrides the info if we have seen the mountpoint
		// earlier in the file. This is correct behavior because the
		// filesystems are listed in mount order.
		mountsByPath[mnt.Path] = mnt

		if mnt.Device != "" {
			mountsByDevice[mnt.Device] = append(mountsByDevice[mnt.Device], mnt)
		}
	}
	mountsInitialized = true
	return nil
}

// AllFilesystems lists all the Mounts on the current system ordered by path.
// Use CheckSetup() to see if they are used with fscrypt.
func AllFilesystems() ([]*Mount, error) {
	mountMutex.Lock()
	defer mountMutex.Unlock()
	if err := loadMountInfo(); err != nil {
		return nil, err
	}

	mounts := make([]*Mount, 0, len(mountsByPath))
	for _, mount := range mountsByPath {
		mounts = append(mounts, mount)
	}

	sort.Sort(PathSorter(mounts))
	return mounts, nil
}

// UpdateMountInfo updates the filesystem mountpoint maps with the current state
// of the filesystem mountpoints. Returns error if the initialization fails.
func UpdateMountInfo() error {
	mountMutex.Lock()
	defer mountMutex.Unlock()
	mountsInitialized = false
	return loadMountInfo()
}

// FindMount returns the corresponding Mount object for some path in a
// filesystem. Note that in the case of a bind mounts there may be two Mount
// objects for the same underlying filesystem. An error is returned if the path
// is invalid or we cannot load the required mount data. If a filesystem has
// been updated since the last call to one of the mount functions, run
// UpdateMountInfo to see changes.
func FindMount(path string) (*Mount, error) {
	path, err := canonicalizePath(path)
	if err != nil {
		return nil, err
	}

	mountMutex.Lock()
	defer mountMutex.Unlock()
	if err = loadMountInfo(); err != nil {
		return nil, err
	}

	// Traverse up the directory tree until we find a mountpoint
	for {
		if mnt, ok := mountsByPath[path]; ok {
			return mnt, nil
		}

		// Move to the parent directory unless we have reached the root.
		parent := filepath.Dir(path)
		if parent == path {
			return nil, errors.Wrap(ErrNotAMountpoint, path)
		}
		path = parent
	}
}

// GetMount returns the Mount object with a matching mountpoint. An error is
// returned if the path is invalid or we cannot load the required mount data. If
// a filesystem has been updated since the last call to one of the mount
// functions, run UpdateMountInfo to see changes.
func GetMount(mountpoint string) (*Mount, error) {
	mountpoint, err := canonicalizePath(mountpoint)
	if err != nil {
		return nil, err
	}

	mountMutex.Lock()
	defer mountMutex.Unlock()
	if err = loadMountInfo(); err != nil {
		return nil, err
	}

	if mnt, ok := mountsByPath[mountpoint]; ok {
		return mnt, nil
	}

	return nil, errors.Wrap(ErrNotAMountpoint, mountpoint)
}

// getMountsFromLink returns the Mount objects which match the provided link.
// This link is formatted as a tag (e.g. <token>=<value>) similar to how they
// appear in "/etc/fstab". Currently, only "UUID" tokens are supported. Note
// that this can match multiple Mounts (due to the existence of bind mounts). An
// error is returned if the link is invalid or we cannot load the required mount
// data. If a filesystem has been updated since the last call to one of the
// mount functions, run UpdateMountInfo to see the change.
func getMountsFromLink(link string) ([]*Mount, error) {
	// Parse the link
	linkComponents := strings.Split(link, "=")
	if len(linkComponents) != 2 {
		return nil, errors.Wrapf(ErrFollowLink, "link %q format is invalid", link)
	}
	token := linkComponents[0]
	value := linkComponents[1]
	if token != uuidToken {
		return nil, errors.Wrapf(ErrFollowLink, "token type %q not supported", token)
	}

	// See if UUID points to an existing device
	searchPath := filepath.Join(uuidDirectory, value)
	if filepath.Base(searchPath) != value {
		return nil, errors.Wrapf(ErrFollowLink, "value %q is not a UUID", value)
	}
	devicePath, err := canonicalizePath(searchPath)
	if err != nil {
		return nil, errors.Wrapf(ErrFollowLink, "no device with UUID %q", value)
	}

	// Lookup mountpoints for device in global store
	mountMutex.Lock()
	defer mountMutex.Unlock()
	if err := loadMountInfo(); err != nil {
		return nil, err
	}
	mnts, ok := mountsByDevice[devicePath]
	if !ok {
		return nil, errors.Wrapf(ErrFollowLink, "no mounts for device %q", devicePath)
	}
	return mnts, nil
}

// makeLink returns a link of the form <token>=<value> where value is the tag
// value for the Mount's device. Currently, only "UUID" tokens are supported. An
// error is returned if the mount has no device, or no UUID.
func makeLink(mnt *Mount, token string) (string, error) {
	if token != uuidToken {
		return "", errors.Wrapf(ErrMakeLink, "token type %q not supported", token)
	}
	if mnt.Device == "" {
		return "", errors.Wrapf(ErrMakeLink, "no device for mount %q", mnt.Path)
	}

	dirContents, err := ioutil.ReadDir(uuidDirectory)
	if err != nil {
		return "", errors.Wrap(ErrMakeLink, err.Error())
	}
	for _, fileInfo := range dirContents {
		if fileInfo.Mode()&os.ModeSymlink == 0 {
			continue // Only interested in UUID symlinks
		}
		uuid := fileInfo.Name()
		devicePath, err := canonicalizePath(filepath.Join(uuidDirectory, uuid))
		if err != nil {
			log.Print(err)
			continue
		}
		if mnt.Device == devicePath {
			return fmt.Sprintf("%s=%s", uuidToken, uuid), nil
		}
	}
	return "", errors.Wrapf(ErrMakeLink, "device %q has no UUID", mnt.Device)
}
<commit_msg>filesystem: make link handling more robust<commit_after>/*
 * mountpoint.go - Contains all the functionality for finding mountpoints and
 * using UUIDs to refer to them. Specifically, we can find the mountpoint of a
 * path, get info about a mountpoint, and find mountpoints with a specific UUID.
 *
 * Copyright 2017 Google Inc.
 * Author: Joe Richey (joerichey@google.com)
 *
 * 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 filesystem

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"

	"github.com/pkg/errors"
)

var (
	// These maps hold data about the state of the system's mountpoints.
	mountsByPath   map[string]*Mount
	mountsByDevice map[DeviceNumber][]*Mount
	// Used to make the mount functions thread safe
	mountMutex sync.Mutex
	// True if the maps have been successfully initialized.
	mountsInitialized bool
	// Supported tokens for filesystem links
	uuidToken = "UUID"
	// Location to perform UUID lookup
	uuidDirectory = "/dev/disk/by-uuid"
)

// Unescape octal-encoded escape sequences in a string from the mountinfo file.
// The kernel encodes the ' ', '\t', '\n', and '\\' bytes this way.  This
// function exactly inverts what the kernel does, including by preserving
// invalid UTF-8.
func unescapeString(str string) string {
	var sb strings.Builder
	for i := 0; i < len(str); i++ {
		b := str[i]
		if b == '\\' && i+3 < len(str) {
			if parsed, err := strconv.ParseInt(str[i+1:i+4], 8, 8); err == nil {
				b = uint8(parsed)
				i += 3
			}
		}
		sb.WriteByte(b)
	}
	return sb.String()
}

// We get the device name via the device number rather than use the mount source
// field directly.  This is necessary to handle a rootfs that was mounted via
// the kernel command line, since mountinfo always shows /dev/root for that.
// This assumes that the device nodes are in the standard location.
func getDeviceName(num DeviceNumber) string {
	linkPath := fmt.Sprintf("/sys/dev/block/%v", num)
	if target, err := os.Readlink(linkPath); err == nil {
		return fmt.Sprintf("/dev/%s", filepath.Base(target))
	}
	return ""
}

// Parse one line of /proc/self/mountinfo.
//
// The line contains the following space-separated fields:
//	[0] mount ID
//	[1] parent ID
//	[2] major:minor
//	[3] root
//	[4] mount point
//	[5] mount options
//	[6...n-1] optional field(s)
//	[n] separator
//	[n+1] filesystem type
//	[n+2] mount source
//	[n+3] super options
//
// For more details, see https://www.kernel.org/doc/Documentation/filesystems/proc.txt
func parseMountInfoLine(line string) *Mount {
	fields := strings.Split(line, " ")
	if len(fields) < 10 {
		return nil
	}

	// Count the optional fields.  In case new fields are appended later,
	// don't simply assume that n == len(fields) - 4.
	n := 6
	for fields[n] != "-" {
		n++
		if n >= len(fields) {
			return nil
		}
	}
	if n+3 >= len(fields) {
		return nil
	}

	var mnt *Mount = &Mount{}
	var err error
	mnt.DeviceNumber, err = newDeviceNumberFromString(fields[2])
	if err != nil {
		return nil
	}
	mnt.Path = unescapeString(fields[4])
	mnt.FilesystemType = unescapeString(fields[n+1])
	mnt.Device = getDeviceName(mnt.DeviceNumber)
	return mnt
}

// loadMountInfo populates the Mount mappings by parsing /proc/self/mountinfo.
// It returns an error if the Mount mappings cannot be populated.
func loadMountInfo() error {
	if mountsInitialized {
		return nil
	}
	mountsByPath = make(map[string]*Mount)
	mountsByDevice = make(map[DeviceNumber][]*Mount)

	file, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return err
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		mnt := parseMountInfoLine(line)
		if mnt == nil {
			log.Printf("ignoring invalid mountinfo line %q", line)
			continue
		}

		// We can only use mountpoints that are directories for fscrypt.
		if !isDir(mnt.Path) {
			log.Printf("ignoring mountpoint %q because it is not a directory", mnt.Path)
			continue
		}

		// Note this overrides the info if we have seen the mountpoint
		// earlier in the file. This is correct behavior because the
		// filesystems are listed in mount order.
		mountsByPath[mnt.Path] = mnt

		mountsByDevice[mnt.DeviceNumber] = append(mountsByDevice[mnt.DeviceNumber], mnt)
	}
	mountsInitialized = true
	return nil
}

// AllFilesystems lists all the Mounts on the current system ordered by path.
// Use CheckSetup() to see if they are used with fscrypt.
func AllFilesystems() ([]*Mount, error) {
	mountMutex.Lock()
	defer mountMutex.Unlock()
	if err := loadMountInfo(); err != nil {
		return nil, err
	}

	mounts := make([]*Mount, 0, len(mountsByPath))
	for _, mount := range mountsByPath {
		mounts = append(mounts, mount)
	}

	sort.Sort(PathSorter(mounts))
	return mounts, nil
}

// UpdateMountInfo updates the filesystem mountpoint maps with the current state
// of the filesystem mountpoints. Returns error if the initialization fails.
func UpdateMountInfo() error {
	mountMutex.Lock()
	defer mountMutex.Unlock()
	mountsInitialized = false
	return loadMountInfo()
}

// FindMount returns the corresponding Mount object for some path in a
// filesystem. Note that in the case of a bind mounts there may be two Mount
// objects for the same underlying filesystem. An error is returned if the path
// is invalid or we cannot load the required mount data. If a filesystem has
// been updated since the last call to one of the mount functions, run
// UpdateMountInfo to see changes.
func FindMount(path string) (*Mount, error) {
	path, err := canonicalizePath(path)
	if err != nil {
		return nil, err
	}

	mountMutex.Lock()
	defer mountMutex.Unlock()
	if err = loadMountInfo(); err != nil {
		return nil, err
	}

	// Traverse up the directory tree until we find a mountpoint
	for {
		if mnt, ok := mountsByPath[path]; ok {
			return mnt, nil
		}

		// Move to the parent directory unless we have reached the root.
		parent := filepath.Dir(path)
		if parent == path {
			return nil, errors.Wrap(ErrNotAMountpoint, path)
		}
		path = parent
	}
}

// GetMount returns the Mount object with a matching mountpoint. An error is
// returned if the path is invalid or we cannot load the required mount data. If
// a filesystem has been updated since the last call to one of the mount
// functions, run UpdateMountInfo to see changes.
func GetMount(mountpoint string) (*Mount, error) {
	mountpoint, err := canonicalizePath(mountpoint)
	if err != nil {
		return nil, err
	}

	mountMutex.Lock()
	defer mountMutex.Unlock()
	if err = loadMountInfo(); err != nil {
		return nil, err
	}

	if mnt, ok := mountsByPath[mountpoint]; ok {
		return mnt, nil
	}

	return nil, errors.Wrap(ErrNotAMountpoint, mountpoint)
}

// getMountsFromLink returns the Mount objects which match the provided link.
// This link is formatted as a tag (e.g. <token>=<value>) similar to how they
// appear in "/etc/fstab". Currently, only "UUID" tokens are supported. Note
// that this can match multiple Mounts (due to the existence of bind mounts). An
// error is returned if the link is invalid or we cannot load the required mount
// data. If a filesystem has been updated since the last call to one of the
// mount functions, run UpdateMountInfo to see the change.
func getMountsFromLink(link string) ([]*Mount, error) {
	// Parse the link
	linkComponents := strings.Split(link, "=")
	if len(linkComponents) != 2 {
		return nil, errors.Wrapf(ErrFollowLink, "link %q format is invalid", link)
	}
	token := linkComponents[0]
	value := linkComponents[1]
	if token != uuidToken {
		return nil, errors.Wrapf(ErrFollowLink, "token type %q not supported", token)
	}

	// See if UUID points to an existing device
	searchPath := filepath.Join(uuidDirectory, value)
	if filepath.Base(searchPath) != value {
		return nil, errors.Wrapf(ErrFollowLink, "value %q is not a UUID", value)
	}
	deviceNumber, err := getDeviceNumber(searchPath)
	if err != nil {
		return nil, errors.Wrapf(ErrFollowLink, "no device with UUID %q", value)
	}

	// Lookup mountpoints for device in global store
	mountMutex.Lock()
	defer mountMutex.Unlock()
	if err := loadMountInfo(); err != nil {
		return nil, err
	}
	mnts, ok := mountsByDevice[deviceNumber]
	if !ok {
		devicePath, _ := canonicalizePath(searchPath)
		return nil, errors.Wrapf(ErrFollowLink, "no mounts for device %q (%v)",
			devicePath, deviceNumber)
	}
	return mnts, nil
}

// makeLink returns a link of the form <token>=<value> where value is the tag
// value for the Mount's device. Currently, only "UUID" tokens are supported. An
// error is returned if the mount has no device, or no UUID.
func makeLink(mnt *Mount, token string) (string, error) {
	if token != uuidToken {
		return "", errors.Wrapf(ErrMakeLink, "token type %q not supported", token)
	}

	dirContents, err := ioutil.ReadDir(uuidDirectory)
	if err != nil {
		return "", errors.Wrap(ErrMakeLink, err.Error())
	}
	for _, fileInfo := range dirContents {
		if fileInfo.Mode()&os.ModeSymlink == 0 {
			continue // Only interested in UUID symlinks
		}
		uuid := fileInfo.Name()
		deviceNumber, err := getDeviceNumber(filepath.Join(uuidDirectory, uuid))
		if err != nil {
			log.Print(err)
			continue
		}
		if mnt.DeviceNumber == deviceNumber {
			return fmt.Sprintf("%s=%s", uuidToken, uuid), nil
		}
	}
	return "", errors.Wrapf(ErrMakeLink, "device %q (%v) has no UUID",
		mnt.Device, mnt.DeviceNumber)
}
<|endoftext|>


<commit_before>package notify

import (
	"context"
	"github.com/godbus/dbus/v5"
	"image"
)

// Notification object paths and interfaces.
const (
	DbusObjectPath               = "/org/freedesktop/Notifications"
	DbusInterfacePath            = "org.freedesktop.Notifications"
	SignalNotificationClosed     = "org.freedesktop.Notifications.NotificationClosed"
	SignalActionInvoked          = "org.freedesktop.Notifications.ActionInvoked"
	CallGetCapabilities          = "org.freedesktop.Notifications.GetCapabilities"
	CallCloseNotification        = "org.freedesktop.Notifications.CloseNotification"
	CallNotify                   = "org.freedesktop.Notifications.Notify"
	CallGetServerInformation     = "org.freedesktop.Notifications.GetServerInformation"
	DbusMemberActionInvoked      = "ActionInvoked"
	DbusMemberNotificationClosed = "NotificationClosed"
)

// Notification expire timeout.
const (
	ExpiresDefault = -1
	ExpiresNever   = 0
)

// Notification Categories
const (
	ClassDevice              = "device"
	ClassDeviceAdded         = "device.added"
	ClassDeviceError         = "device.error"
	ClassDeviceRemoved       = "device.removed"
	ClassEmail               = "email"
	ClassEmailArrived        = "email.arrived"
	ClassEmailBounced        = "email.bounced"
	ClassIm                  = "im"
	ClassImError             = "im.error"
	ClassImReceived          = "im.received"
	ClassNetwork             = "network"
	ClassNetworkConnected    = "network.connected"
	ClassNetworkDisconnected = "network.disconnected"
	ClassNetworkError        = "network.error"
	ClassPresence            = "presence"
	ClassPresenceOffline     = "presence.offline"
	ClassPresenceOnline      = "presence.online"
	ClassTransfer            = "transfer"
	ClassTransferComplete    = "transfer.complete"
	ClassTransferError       = "transfer.error"
)

// Urgency Levels
const (
	UrgencyLow      = byte(0)
	UrgencyNormal   = byte(1)
	UrgencyCritical = byte(2)
)

// Hints
const (
	HintActionIcons   = "action-icons"
	HintCategory      = "category"
	HintDesktopEntry  = "desktop-entry"
	HintImageData     = "image-data"
	HintImagePath     = "image-path"
	HintResident      = "resident"
	HintSoundFile     = "sound-file"
	HintSoundName     = "sound-name"
	HintSuppressSound = "suppress-sound"
	HintTransient     = "transient"
	HintX             = "x"
	HintY             = "y"
	HintUrgency       = "urgency"
)

// Capabilities is a struct containing the capabilities of the notification
// server.
type Capabilities struct {
	// Supports using icons instead of text for displaying actions.
	ActionIcons bool

	// The server will provide any specified actions to the user.
	Actions bool

	// Supports body text. Some implementations may only show the summary.
	Body bool

	// The server supports hyperlinks in the notifications.
	BodyHyperlinks bool

	// The server supports images in the notifications.
	BodyImages bool

	// Supports markup in the body text.
	BodyMarkup bool

	// The server will render an animation of all the frames in a given
	// image array.
	IconMulti bool

	// Supports display of exactly 1 frame of any given image array.
	IconStatic bool

	// The server supports persistence of notifications. Notifications will
	// be retained until they are acknowledged or removed by the user or
	// recalled by the sender.
	Persistence bool

	// The server supports sounds on notifications.
	Sound bool
}

// GetCapabilities returns the capabilities of the notification server.
func GetCapabilities() (c Capabilities, err error) {
	conn, err := dbus.SessionBus()
	if err != nil {
		return
	}

	var d = make(chan *dbus.Call, 1)
	var o = conn.Object(DbusInterfacePath, DbusObjectPath)
	var s = make([]string, 0)
	o.GoWithContext(context.Background(),
		CallGetCapabilities,
		0,
		d)
	err = (<-d).Store(&s)
	if err != nil {
		return
	}

	for _, v := range s {
		switch v {
		case "action-icons":
			c.ActionIcons = true
			break
		case "actions":
			c.Actions = true
			break
		case "body":
			c.Body = true
			break
		case "body-hyperlinks":
			c.BodyHyperlinks = true
			break
		case "body-images":
			c.BodyImages = true
			break
		case "body-markup":
			c.BodyMarkup = true
			break
		case "icon-multi":
			c.IconMulti = true
			break
		case "icon-static":
			c.IconStatic = true
			break
		case "persistence":
			c.Persistence = true
			break
		case "sound":
			c.Sound = true
			break
		}
	}
	return
}

// ServerInformation is a struct containing information about the server such
// as its name and version.
type ServerInformation struct {
	// The name of the notification server daemon
	Name string

	// The vendor of the notification server
	Vendor string

	// Version of the notification server
	Version string

	// Spec version the notification server conforms to
	SpecVersion string
}

// GetServerInformation returns information about the notification server such
// as its name and version.
func GetServerInformation() (i ServerInformation, err error) {
	conn, err := dbus.SessionBus()
	if err != nil {
		return
	}
	var d = make(chan *dbus.Call, 1)
	var o = conn.Object(DbusInterfacePath, DbusObjectPath)
	o.GoWithContext(context.Background(),
		CallGetServerInformation,
		0,
		d)
	err = (<-d).Store(&i.Name,
		&i.Vendor,
		&i.Version,
		&i.SpecVersion)
	return
}

// Notification is a struct which describes the notification to be displayed
// by the notification server.
type Notification struct {
	// The optional name of the application sending the notification.
	// Can be blank.
	AppName string

	// The optional notification ID that this notification replaces.
	ReplacesID uint32

	// The optional program icon of the calling application.
	AppIcon string

	// The summary text briefly describing the notification.
	Summary string

	// The optional detailed body text.
	Body string

	// The actions send a request message back to the notification client
	// when invoked.
	Actions []string

	// Hints are a way to provide extra data to a notification server.
	Hints map[string]interface{}

	// The timeout time in milliseconds since the display of the
	// notification at which the notification should automatically close.
	Timeout int32

	hints map[string]dbus.Variant
}

// NewNotification creates a new notification object with some basic
// information.
func NewNotification(summary, body string) Notification {
	return Notification{
		Body:    body,
		Summary: summary,
		Timeout: ExpiresDefault,
		hints:   make(map[string]dbus.Variant),
	}
}

// Show sends the information in the notification object to the server to be
// displayed.
func (x *Notification) Show() (id uint32, err error) {
	conn, err := dbus.SessionBus()
	if err != nil {
		return
	}

	// We need to convert the interface type of the map to dbus.Variant as
	// people dont want to have to import the dbus package just to make use
	// of the notification hints.
	for k, v := range x.Hints {
		x.hints[k] = dbus.MakeVariant(v)
	}

	var d = make(chan *dbus.Call, 1)
	var o = conn.Object(DbusInterfacePath, DbusObjectPath)
	o.GoWithContext(context.Background(),
		CallNotify,
		0,
		d,
		x.AppName,
		x.ReplacesID,
		x.AppIcon,
		x.Summary,
		x.Body,
		x.Actions,
		x.hints,
		x.Timeout)
	err = (<-d).Store(&id)
	return
}

// _ImageData needs to resemble the (iiibiiay) signature.
type _ImageData struct {
	/*0*/ Width int
	/*1*/ Height int
	/*2*/ RowStride int
	/*3*/ HasAlpha bool
	/*4*/ BitsPerSample int
	/*5*/ Samples int
	/*6*/ Image []byte
}

type ImageError struct{}

func (x ImageError) Error() string {
	return "Given image.Image was not of type *image.RGBA"
}

func (x *Notification) SetImage(img image.Image) (err error) {
	if p, ok := img.(*image.RGBA); ok {
		var r = p.Bounds()
		var d = _ImageData{
			r.Max.X, // Width
			r.Max.Y, // Height
			p.Stride,
			true,
			8,
			4,
			p.Pix,
		}
		x.hints["image-data"] = dbus.MakeVariant(d)
		return
	}
	err = ImageError{}
	return
}

// CloseNotification closes the notification if it exists using its id.
func CloseNotification(id uint32) (err error) {
	conn, err := dbus.SessionBus()
	if err != nil {
		return
	}
	var d = make(chan *dbus.Call, 1)
	var o = conn.Object(DbusInterfacePath, DbusObjectPath)
	o.GoWithContext(context.Background(),
		CallCloseNotification,
		0,
		d,
		id)
	err = (<-d).Err
	return
}
<commit_msg>Add doc for SetImage.<commit_after>package notify

import (
	"context"
	"github.com/godbus/dbus/v5"
	"image"
)

// Notification object paths and interfaces.
const (
	DbusObjectPath               = "/org/freedesktop/Notifications"
	DbusInterfacePath            = "org.freedesktop.Notifications"
	SignalNotificationClosed     = "org.freedesktop.Notifications.NotificationClosed"
	SignalActionInvoked          = "org.freedesktop.Notifications.ActionInvoked"
	CallGetCapabilities          = "org.freedesktop.Notifications.GetCapabilities"
	CallCloseNotification        = "org.freedesktop.Notifications.CloseNotification"
	CallNotify                   = "org.freedesktop.Notifications.Notify"
	CallGetServerInformation     = "org.freedesktop.Notifications.GetServerInformation"
	DbusMemberActionInvoked      = "ActionInvoked"
	DbusMemberNotificationClosed = "NotificationClosed"
)

// Notification expire timeout.
const (
	ExpiresDefault = -1
	ExpiresNever   = 0
)

// Notification Categories
const (
	ClassDevice              = "device"
	ClassDeviceAdded         = "device.added"
	ClassDeviceError         = "device.error"
	ClassDeviceRemoved       = "device.removed"
	ClassEmail               = "email"
	ClassEmailArrived        = "email.arrived"
	ClassEmailBounced        = "email.bounced"
	ClassIm                  = "im"
	ClassImError             = "im.error"
	ClassImReceived          = "im.received"
	ClassNetwork             = "network"
	ClassNetworkConnected    = "network.connected"
	ClassNetworkDisconnected = "network.disconnected"
	ClassNetworkError        = "network.error"
	ClassPresence            = "presence"
	ClassPresenceOffline     = "presence.offline"
	ClassPresenceOnline      = "presence.online"
	ClassTransfer            = "transfer"
	ClassTransferComplete    = "transfer.complete"
	ClassTransferError       = "transfer.error"
)

// Urgency Levels
const (
	UrgencyLow      = byte(0)
	UrgencyNormal   = byte(1)
	UrgencyCritical = byte(2)
)

// Hints
const (
	HintActionIcons   = "action-icons"
	HintCategory      = "category"
	HintDesktopEntry  = "desktop-entry"
	HintImageData     = "image-data"
	HintImagePath     = "image-path"
	HintResident      = "resident"
	HintSoundFile     = "sound-file"
	HintSoundName     = "sound-name"
	HintSuppressSound = "suppress-sound"
	HintTransient     = "transient"
	HintX             = "x"
	HintY             = "y"
	HintUrgency       = "urgency"
)

// Capabilities is a struct containing the capabilities of the notification
// server.
type Capabilities struct {
	// Supports using icons instead of text for displaying actions.
	ActionIcons bool

	// The server will provide any specified actions to the user.
	Actions bool

	// Supports body text. Some implementations may only show the summary.
	Body bool

	// The server supports hyperlinks in the notifications.
	BodyHyperlinks bool

	// The server supports images in the notifications.
	BodyImages bool

	// Supports markup in the body text.
	BodyMarkup bool

	// The server will render an animation of all the frames in a given
	// image array.
	IconMulti bool

	// Supports display of exactly 1 frame of any given image array.
	IconStatic bool

	// The server supports persistence of notifications. Notifications will
	// be retained until they are acknowledged or removed by the user or
	// recalled by the sender.
	Persistence bool

	// The server supports sounds on notifications.
	Sound bool
}

// GetCapabilities returns the capabilities of the notification server.
func GetCapabilities() (c Capabilities, err error) {
	conn, err := dbus.SessionBus()
	if err != nil {
		return
	}

	var d = make(chan *dbus.Call, 1)
	var o = conn.Object(DbusInterfacePath, DbusObjectPath)
	var s = make([]string, 0)
	o.GoWithContext(context.Background(),
		CallGetCapabilities,
		0,
		d)
	err = (<-d).Store(&s)
	if err != nil {
		return
	}

	for _, v := range s {
		switch v {
		case "action-icons":
			c.ActionIcons = true
			break
		case "actions":
			c.Actions = true
			break
		case "body":
			c.Body = true
			break
		case "body-hyperlinks":
			c.BodyHyperlinks = true
			break
		case "body-images":
			c.BodyImages = true
			break
		case "body-markup":
			c.BodyMarkup = true
			break
		case "icon-multi":
			c.IconMulti = true
			break
		case "icon-static":
			c.IconStatic = true
			break
		case "persistence":
			c.Persistence = true
			break
		case "sound":
			c.Sound = true
			break
		}
	}
	return
}

// ServerInformation is a struct containing information about the server such
// as its name and version.
type ServerInformation struct {
	// The name of the notification server daemon
	Name string

	// The vendor of the notification server
	Vendor string

	// Version of the notification server
	Version string

	// Spec version the notification server conforms to
	SpecVersion string
}

// GetServerInformation returns information about the notification server such
// as its name and version.
func GetServerInformation() (i ServerInformation, err error) {
	conn, err := dbus.SessionBus()
	if err != nil {
		return
	}
	var d = make(chan *dbus.Call, 1)
	var o = conn.Object(DbusInterfacePath, DbusObjectPath)
	o.GoWithContext(context.Background(),
		CallGetServerInformation,
		0,
		d)
	err = (<-d).Store(&i.Name,
		&i.Vendor,
		&i.Version,
		&i.SpecVersion)
	return
}

// Notification is a struct which describes the notification to be displayed
// by the notification server.
type Notification struct {
	// The optional name of the application sending the notification.
	// Can be blank.
	AppName string

	// The optional notification ID that this notification replaces.
	ReplacesID uint32

	// The optional program icon of the calling application.
	AppIcon string

	// The summary text briefly describing the notification.
	Summary string

	// The optional detailed body text.
	Body string

	// The actions send a request message back to the notification client
	// when invoked.
	Actions []string

	// Hints are a way to provide extra data to a notification server.
	Hints map[string]interface{}

	// The timeout time in milliseconds since the display of the
	// notification at which the notification should automatically close.
	Timeout int32

	hints map[string]dbus.Variant
}

// NewNotification creates a new notification object with some basic
// information.
func NewNotification(summary, body string) Notification {
	return Notification{
		Body:    body,
		Summary: summary,
		Timeout: ExpiresDefault,
		hints:   make(map[string]dbus.Variant),
	}
}

// Show sends the information in the notification object to the server to be
// displayed.
func (x *Notification) Show() (id uint32, err error) {
	conn, err := dbus.SessionBus()
	if err != nil {
		return
	}

	// We need to convert the interface type of the map to dbus.Variant as
	// people dont want to have to import the dbus package just to make use
	// of the notification hints.
	for k, v := range x.Hints {
		x.hints[k] = dbus.MakeVariant(v)
	}

	var d = make(chan *dbus.Call, 1)
	var o = conn.Object(DbusInterfacePath, DbusObjectPath)
	o.GoWithContext(context.Background(),
		CallNotify,
		0,
		d,
		x.AppName,
		x.ReplacesID,
		x.AppIcon,
		x.Summary,
		x.Body,
		x.Actions,
		x.hints,
		x.Timeout)
	err = (<-d).Store(&id)
	return
}

// _ImageData needs to resemble the (iiibiiay) signature.
type _ImageData struct {
	/*0*/ Width int
	/*1*/ Height int
	/*2*/ RowStride int
	/*3*/ HasAlpha bool
	/*4*/ BitsPerSample int
	/*5*/ Samples int
	/*6*/ Image []byte
}

type ImageError struct{}

func (x ImageError) Error() string {
	return "Given image.Image was not of type *image.RGBA"
}

// SetImage sets the image in the notification from an image.Image
// interface which must have an underlying type of *image.RGBA.
// Only the RGBA color space is allowed as only that is supported
// by the gdk-pixbuf library.
func (x *Notification) SetImage(img image.Image) (err error) {
	if p, ok := img.(*image.RGBA); ok {
		var r = p.Bounds()
		var d = _ImageData{
			r.Max.X, // Width
			r.Max.Y, // Height
			p.Stride,
			true,
			8,
			4,
			p.Pix,
		}
		x.hints["image-data"] = dbus.MakeVariant(d)
		return
	}
	err = ImageError{}
	return
}

// CloseNotification closes the notification if it exists using its id.
func CloseNotification(id uint32) (err error) {
	conn, err := dbus.SessionBus()
	if err != nil {
		return
	}
	var d = make(chan *dbus.Call, 1)
	var o = conn.Object(DbusInterfacePath, DbusObjectPath)
	o.GoWithContext(context.Background(),
		CallCloseNotification,
		0,
		d,
		id)
	err = (<-d).Err
	return
}
<|endoftext|>


<commit_before>package flag9

import "fmt"

func ExampleArgs() {
	s := []string{"-ab", "-carg", "--", "-d"}
	args := NewArgs(s)
	for args.Next() {
		switch args.Argc() {
		case 'a', 'b':
			fmt.Printf("%c\n", args.Argc())
		case 'c':
			argf, ok := args.Argf()
			if ok {
				fmt.Println("c", argf)
			} else {
				fmt.Println("c without argument")
			}
		case 'd':
			fmt.Println("not reached")
		}
	}
	fmt.Println(args.Argv())
	// Output:
	// a
	// b
	// c arg
	// [-d]
}
<commit_msg>Add unit tests<commit_after>package flag9

import (
	"fmt"
	"testing"
)

func ExampleArgs() {
	s := []string{"-ab", "-carg", "--", "-d"}
	args := NewArgs(s)
	for args.Next() {
		switch args.Argc() {
		case 'a', 'b':
			fmt.Printf("%c\n", args.Argc())
		case 'c':
			argf, ok := args.Argf()
			if ok {
				fmt.Println("c", argf)
			} else {
				fmt.Println("c without argument")
			}
		case 'd':
			fmt.Println("not reached")
		}
	}
	fmt.Println(args.Argv())
	// Output:
	// a
	// b
	// c arg
	// [-d]
}

func TestFlag9(t *testing.T) {
	type parg struct {
		c       rune
		hasArgf bool
		argf    string
	}
	type test struct {
		args  []string
		pargs []parg
		argv  []string
	}
	tests := []test{
		test{
			[]string{"-bfoo", "-a", "bar", "-", "-d"},
			[]parg{
				parg{'b', true, "foo"},
				parg{'a', true, "bar"},
			},
			[]string{"-", "-d"},
		},
		test{
			[]string{},
			[]parg{},
			[]string{},
		},
		test{
			[]string{"--"},
			[]parg{},
			[]string{},
		},
		test{
			[]string{"-"},
			[]parg{},
			[]string{"-"},
		},
		test{
			[]string{"foo"},
			[]parg{},
			[]string{"foo"},
		},
		test{
			[]string{"-abcfoo", "-c", "bar", "-c", "baz"},
			[]parg{
				parg{'a', false, ""},
				parg{'b', false, ""},
				parg{'c', true, "foo"},
				parg{'c', true, "bar"},
				parg{'c', false, ""},
			},
			[]string{"baz"},
		},
	}
	for i, v := range tests {
		args := NewArgs(v.args)
		for args.Next() {
			if len(v.pargs) == 0 {
				t.Fatalf("test %d: too many parsed arguments", i)
			}
			c := args.Argc()
			if c != v.pargs[0].c {
				t.Fatalf("test %d: got option %c, wanted %c", i, c, v.pargs[0].c)
			}
			if v.pargs[0].hasArgf {
				argf, ok := args.Argf()
				if !ok {
					t.Fatalf("test %d: expected argf, but got none", i)
				}
				if argf != v.pargs[0].argf {
					t.Fatalf("test %d: got argf %q, but expected %q", i, argf,
						v.pargs[0].argf)
				}
			}
			v.pargs = v.pargs[1:]
		}
		if len(v.pargs) != 0 {
			t.Fatalf("test %d: %d unparsed options", i, len(v.pargs))
		}
		argv := args.Argv()
		if len(argv) != len(v.argv) {
			t.Fatalf("test %d: argv count mismatch (got %d, expected %d)", i,
				len(argv), len(v.argv))
		}
		for n := range argv {
			if argv[n] != v.argv[n] {
				t.Fatalf("test %d: argv[%d] mismatch (got %q, expected %q)", i,
					n, argv[n], v.argv[n])
			}
		}
	}
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"sync"
)

const (
	commandName = "mdt"
)

type platform struct {
	os   string
	arch string
}

type binary struct {
	name    string
	version string
	targets []platform
}

// Name returns the name of the binary with a certain format for a platform.
func (bin binary) Name(os, arch string) string {
	s := fmt.Sprintf("%s%s.%s-%s", bin.name, bin.version, os, arch)
	if os == "windows" {
		s = s + ".exe"
	}
	return s
}

// Names returns the name of the binary for all the target platforms.
func (bin binary) Names() []string {
	names := make([]string, len(bin.targets))
	for i, t := range bin.targets {
		names[i] = bin.Name(t.os, t.arch)
	}
	return names
}

var (
	release   = flag.Bool("release", false, "build binaries for all target platforms")
	clean     = flag.Bool("clean", false, "remove all created binaries from current directory")
	buildOS   = flag.String("os", runtime.GOOS, "set operating system to build for")
	buildArch = flag.String("arch", runtime.GOARCH, "set architecture to build for")
)

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: go run make.go [OPTIONS]\n\n")
	fmt.Fprintln(os.Stderr, "OPTIONS:")
	flag.PrintDefaults()
}

func main() {
	flag.Usage = usage
	flag.Parse()

	bin := binary{
		name: commandName,
		targets: []platform{
			{os: "linux", arch: "386"}, {os: "linux", arch: "amd64"},
			{os: "windows", arch: "386"}, {os: "windows", arch: "amd64"},
			{os: "darwin", arch: "386"}, {os: "darwin", arch: "amd64"},
		},
	}
	bin.version = getVersion()

	if *release {
		forEachBinTarget(bin, buildBinary)
		os.Exit(0)
	}

	if *clean {
		forEachBinTarget(bin, rmBinary)
		os.Exit(0)
	}

	buildBinary(bin, *buildOS, *buildArch)
}

// getVersion returns the version of the project using git.
func getVersion() string {
	cmd := exec.Command("git", "describe", "--tags", "--always")
	cmd.Stderr = os.Stderr
	out, err := cmd.Output()
	if err != nil {
		log.Fatalf("Error running git describe: %v", err)
	}
	return strings.TrimPrefix(strings.TrimSpace(string(out)), "v")
}

type binaryFunc func(bin binary, OS, arch string)

// forEachBinTarget runs a function for all the target platforms of a binary in
// parallel.
func forEachBinTarget(bin binary, fn binaryFunc) {
	var wg sync.WaitGroup
	for _, t := range bin.targets {
		wg.Add(1)
		go func(bin binary, os, arch string) {
			defer wg.Done()
			fn(bin, os, arch)
		}(bin, t.os, t.arch)
	}
	wg.Wait()
}

// buildBinary builds a binary for a certain operating system and architecture
// combination while using --ldflags to set the binary's version variable.
func buildBinary(bin binary, OS, arch string) {
	ldflags := fmt.Sprintf("--ldflags=-X github.com/nstratos/mdt/ui.Version=%s", bin.version)
	cmd := exec.Command("go", "build", ldflags, "-o", bin.Name(OS, arch))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	cmd.Env = copyOSEnv()
	cmd.Env = setEnv(cmd.Env, "GOOS", OS)
	cmd.Env = setEnv(cmd.Env, "GOARCH", arch)
	fmt.Println("Building binary:", bin.Name(OS, arch))
	if err := cmd.Run(); err != nil {
		log.Fatalln("Error running go build:", err)
	}
}

// rmBinary removes a binary from the current directory.
func rmBinary(bin binary, OS, arch string) {
	err := os.Remove(bin.Name(OS, arch))
	if err != nil {
		if !os.IsNotExist(err) {
			fmt.Fprintln(os.Stderr, "Error removing binary:", err)
		}
	}
}

// copyOSEnv returns a copy of the system's environment variables.
func copyOSEnv() (environ []string) {
	return append(environ, os.Environ()...)
}

// setEnv searches in a slice of environment variables with the form key=value
// for a key and if found it sets its value, otherwise it adds the pair.
func setEnv(environ []string, key, value string) []string {
	for i, env := range environ {
		if strings.Split(env, "=")[0] == key {
			environ[i] = fmt.Sprintf("%s=%s", key, value)
			return environ
		}
	}
	return append(environ, fmt.Sprintf("%s=%s", key, value))
}
<commit_msg>Remove darwin from target platforms<commit_after>// +build ignore

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"sync"
)

const (
	commandName = "mdt"
)

type platform struct {
	os   string
	arch string
}

type binary struct {
	name    string
	version string
	targets []platform
}

// Name returns the name of the binary with a certain format for a platform.
func (bin binary) Name(os, arch string) string {
	s := fmt.Sprintf("%s%s.%s-%s", bin.name, bin.version, os, arch)
	if os == "windows" {
		s = s + ".exe"
	}
	return s
}

// Names returns the name of the binary for all the target platforms.
func (bin binary) Names() []string {
	names := make([]string, len(bin.targets))
	for i, t := range bin.targets {
		names[i] = bin.Name(t.os, t.arch)
	}
	return names
}

var (
	release   = flag.Bool("release", false, "build binaries for all target platforms")
	clean     = flag.Bool("clean", false, "remove all created binaries from current directory")
	buildOS   = flag.String("os", runtime.GOOS, "set operating system to build for")
	buildArch = flag.String("arch", runtime.GOARCH, "set architecture to build for")
)

func usage() {
	fmt.Fprintf(os.Stderr, "Usage: go run make.go [OPTIONS]\n\n")
	fmt.Fprintln(os.Stderr, "OPTIONS:")
	flag.PrintDefaults()
}

func main() {
	flag.Usage = usage
	flag.Parse()

	bin := binary{
		name: commandName,
		targets: []platform{
			{os: "linux", arch: "386"}, {os: "linux", arch: "amd64"},
			{os: "windows", arch: "386"}, {os: "windows", arch: "amd64"},
		},
	}
	bin.version = getVersion()

	if *release {
		forEachBinTarget(bin, buildBinary)
		os.Exit(0)
	}

	if *clean {
		forEachBinTarget(bin, rmBinary)
		os.Exit(0)
	}

	buildBinary(bin, *buildOS, *buildArch)
}

// getVersion returns the version of the project using git.
func getVersion() string {
	cmd := exec.Command("git", "describe", "--tags", "--always")
	cmd.Stderr = os.Stderr
	out, err := cmd.Output()
	if err != nil {
		log.Fatalf("Error running git describe: %v", err)
	}
	return strings.TrimPrefix(strings.TrimSpace(string(out)), "v")
}

type binaryFunc func(bin binary, OS, arch string)

// forEachBinTarget runs a function for all the target platforms of a binary in
// parallel.
func forEachBinTarget(bin binary, fn binaryFunc) {
	var wg sync.WaitGroup
	for _, t := range bin.targets {
		wg.Add(1)
		go func(bin binary, os, arch string) {
			defer wg.Done()
			fn(bin, os, arch)
		}(bin, t.os, t.arch)
	}
	wg.Wait()
}

// buildBinary builds a binary for a certain operating system and architecture
// combination while using --ldflags to set the binary's version variable.
func buildBinary(bin binary, OS, arch string) {
	ldflags := fmt.Sprintf("--ldflags=-X github.com/nstratos/mdt/ui.Version=%s", bin.version)
	cmd := exec.Command("go", "build", ldflags, "-o", bin.Name(OS, arch))
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	cmd.Env = copyOSEnv()
	cmd.Env = setEnv(cmd.Env, "GOOS", OS)
	cmd.Env = setEnv(cmd.Env, "GOARCH", arch)
	fmt.Println("Building binary:", bin.Name(OS, arch))
	if err := cmd.Run(); err != nil {
		log.Fatalln("Error running go build:", err)
	}
}

// rmBinary removes a binary from the current directory.
func rmBinary(bin binary, OS, arch string) {
	err := os.Remove(bin.Name(OS, arch))
	if err != nil {
		if !os.IsNotExist(err) {
			fmt.Fprintln(os.Stderr, "Error removing binary:", err)
		}
	}
}

// copyOSEnv returns a copy of the system's environment variables.
func copyOSEnv() (environ []string) {
	return append(environ, os.Environ()...)
}

// setEnv searches in a slice of environment variables with the form key=value
// for a key and if found it sets its value, otherwise it adds the pair.
func setEnv(environ []string, key, value string) []string {
	for i, env := range environ {
		if strings.Split(env, "=")[0] == key {
			environ[i] = fmt.Sprintf("%s=%s", key, value)
			return environ
		}
	}
	return append(environ, fmt.Sprintf("%s=%s", key, value))
}
<|endoftext|>


<commit_before>package cdb

import (
	"bufio"
	"encoding/binary"
	"errors"
	"io"
	"os"
	"strconv"
)

var BadFormatError = errors.New("bad format")

// Make reads cdb-formatted records from r and writes a cdb-format database
// to w.  See the documentation for Dump for details on the input record format.
func Make(w io.WriteSeeker, r io.Reader) (err error) {
	defer func() { // Centralize error handling.
		if e := recover(); e != nil {
			err = e.(error)
		}
	}()

	if _, err = w.Seek(int64(headerSize), 0); err != nil {
		return
	}

	buf := make([]byte, 8)
	rb := bufio.NewReader(r)
	wb := bufio.NewWriter(w)
	hash := cdbHash()
	hw := io.MultiWriter(hash, wb) // Computes hash when writing record key.
	rr := &recReader{rb}
	htables := make(map[uint32][]slot)
	pos := headerSize
	// Read all records and write to output.
	for {
		// Record format is "+klen,dlen:key->data\n"
		c := rr.readByte()
		if c == '\n' { // end of records
			break
		}
		if c != '+' {
			return BadFormatError
		}
		klen, dlen := rr.readNum(','), rr.readNum(':')
		writeNums(wb, klen, dlen, buf)
		hash.Reset()
		rr.copyn(hw, klen)
		rr.eatByte('-')
		rr.eatByte('>')
		rr.copyn(wb, dlen)
		rr.eatByte('\n')
		h := hash.Sum32()
		tableNum := h % 256
		if htables[tableNum] == nil {
			htables[tableNum] = make([]slot, 1)
		}
		htables[tableNum] = append(htables[tableNum], slot{h, pos})
		pos += 8 + klen + dlen
	}

	// Write hash tables and header.

	// Create and reuse a single hash table.
	maxSlots := 0
	for _, slots := range htables {
		if len(slots) > maxSlots {
			maxSlots = len(slots)
		}
	}
	slotTable := make([]slot, maxSlots*2)

	header := make([]byte, headerSize)
	// Write hash tables.
	for i := uint32(0); i < 256; i++ {
		slots := htables[i]
		if slots == nil {
			putNum(header[i*8:], pos)
			continue
		}

		nslots := uint32(len(slots) * 2)
		hashSlotTable := slotTable[:nslots]
		// Reset table slots.
		for j := 0; j < len(hashSlotTable); j++ {
			hashSlotTable[j].h = 0
			hashSlotTable[j].pos = 0
		}

		for _, slot := range slots {
			slotPos := (slot.h / 256) % nslots
			for hashSlotTable[slotPos].pos != 0 {
				slotPos++
				if slotPos == uint32(len(hashSlotTable)) {
					slotPos = 0
				}
			}
			hashSlotTable[slotPos] = slot
		}

		if err = writeSlots(wb, hashSlotTable, buf); err != nil {
			return
		}

		putNum(header[i*8:], pos)
		putNum(header[i*8+4:], nslots)
		pos += 8 * nslots
	}

	if err = wb.Flush(); err != nil {
		return
	}

	if _, err = w.Seek(0, 0); err != nil {
		return
	}

	_, err = w.Write(header)

	return
}

type recReader struct {
	*bufio.Reader
}

func (rr *recReader) readByte() byte {
	c, err := rr.ReadByte()
	if err != nil {
		panic(err)
	}

	return c
}

func (rr *recReader) eatByte(c byte) {
	if rr.readByte() != c {
		panic(errors.New("unexpected character"))
	}
}

// There is no strconv.Atoui32, so make one here.
func atoui32(s string) (n uint32, err error) {
	iu64, err := strconv.Atoui64(s)
	if err != nil {
		return 0, err
	}

	n = uint32(iu64)
	if uint64(n) != iu64 {
		return 0, &strconv.NumError{s, os.ERANGE}
	}

	return n, nil
}

func (rr *recReader) readNum(delim byte) uint32 {
	s, err := rr.ReadString(delim)
	if err != nil {
		panic(err)
	}

	s = s[:len(s)-1] // Strip delim
	n, err := atoui32(s)
	if err != nil {
		panic(err)
	}

	return n
}

func (rr *recReader) copyn(w io.Writer, n uint32) {
	if _, err := io.CopyN(w, rr, int64(n)); err != nil {
		panic(err)
	}
}

func putNum(buf []byte, x uint32) {
	binary.LittleEndian.PutUint32(buf, x)
}

func writeNums(w io.Writer, x, y uint32, buf []byte) {
	putNum(buf, x)
	putNum(buf[4:], y)
	if _, err := w.Write(buf[:8]); err != nil {
		panic(err)
	}
}

type slot struct {
	h, pos uint32
}

func writeSlots(w io.Writer, slots []slot, buf []byte) (err error) {
	for _, np := range slots {
		putNum(buf, np.h)
		putNum(buf[4:], np.pos)
		if _, err = w.Write(buf[:8]); err != nil {
			return
		}
	}

	return nil
}
<commit_msg>Use strconv.ParseUint<commit_after>package cdb

import (
	"bufio"
	"encoding/binary"
	"errors"
	"io"
	"strconv"
)

var BadFormatError = errors.New("bad format")

// Make reads cdb-formatted records from r and writes a cdb-format database
// to w.  See the documentation for Dump for details on the input record format.
func Make(w io.WriteSeeker, r io.Reader) (err error) {
	defer func() { // Centralize error handling.
		if e := recover(); e != nil {
			err = e.(error)
		}
	}()

	if _, err = w.Seek(int64(headerSize), 0); err != nil {
		return
	}

	buf := make([]byte, 8)
	rb := bufio.NewReader(r)
	wb := bufio.NewWriter(w)
	hash := cdbHash()
	hw := io.MultiWriter(hash, wb) // Computes hash when writing record key.
	rr := &recReader{rb}
	htables := make(map[uint32][]slot)
	pos := headerSize
	// Read all records and write to output.
	for {
		// Record format is "+klen,dlen:key->data\n"
		c := rr.readByte()
		if c == '\n' { // end of records
			break
		}
		if c != '+' {
			return BadFormatError
		}
		klen, dlen := rr.readNum(','), rr.readNum(':')
		writeNums(wb, klen, dlen, buf)
		hash.Reset()
		rr.copyn(hw, klen)
		rr.eatByte('-')
		rr.eatByte('>')
		rr.copyn(wb, dlen)
		rr.eatByte('\n')
		h := hash.Sum32()
		tableNum := h % 256
		if htables[tableNum] == nil {
			htables[tableNum] = make([]slot, 1)
		}
		htables[tableNum] = append(htables[tableNum], slot{h, pos})
		pos += 8 + klen + dlen
	}

	// Write hash tables and header.

	// Create and reuse a single hash table.
	maxSlots := 0
	for _, slots := range htables {
		if len(slots) > maxSlots {
			maxSlots = len(slots)
		}
	}
	slotTable := make([]slot, maxSlots*2)

	header := make([]byte, headerSize)
	// Write hash tables.
	for i := uint32(0); i < 256; i++ {
		slots := htables[i]
		if slots == nil {
			putNum(header[i*8:], pos)
			continue
		}

		nslots := uint32(len(slots) * 2)
		hashSlotTable := slotTable[:nslots]
		// Reset table slots.
		for j := 0; j < len(hashSlotTable); j++ {
			hashSlotTable[j].h = 0
			hashSlotTable[j].pos = 0
		}

		for _, slot := range slots {
			slotPos := (slot.h / 256) % nslots
			for hashSlotTable[slotPos].pos != 0 {
				slotPos++
				if slotPos == uint32(len(hashSlotTable)) {
					slotPos = 0
				}
			}
			hashSlotTable[slotPos] = slot
		}

		if err = writeSlots(wb, hashSlotTable, buf); err != nil {
			return
		}

		putNum(header[i*8:], pos)
		putNum(header[i*8+4:], nslots)
		pos += 8 * nslots
	}

	if err = wb.Flush(); err != nil {
		return
	}

	if _, err = w.Seek(0, 0); err != nil {
		return
	}

	_, err = w.Write(header)

	return
}

type recReader struct {
	*bufio.Reader
}

func (rr *recReader) readByte() byte {
	c, err := rr.ReadByte()
	if err != nil {
		panic(err)
	}

	return c
}

func (rr *recReader) eatByte(c byte) {
	if rr.readByte() != c {
		panic(errors.New("unexpected character"))
	}
}

func (rr *recReader) readNum(delim byte) uint32 {
	s, err := rr.ReadString(delim)
	if err != nil {
		panic(err)
	}

	s = s[:len(s)-1] // Strip delim
	n, err := strconv.ParseUint(s, 10, 32)
	if err != nil {
		panic(err)
	}

	return uint32(n)
}

func (rr *recReader) copyn(w io.Writer, n uint32) {
	if _, err := io.CopyN(w, rr, int64(n)); err != nil {
		panic(err)
	}
}

func putNum(buf []byte, x uint32) {
	binary.LittleEndian.PutUint32(buf, x)
}

func writeNums(w io.Writer, x, y uint32, buf []byte) {
	putNum(buf, x)
	putNum(buf[4:], y)
	if _, err := w.Write(buf[:8]); err != nil {
		panic(err)
	}
}

type slot struct {
	h, pos uint32
}

func writeSlots(w io.Writer, slots []slot, buf []byte) (err error) {
	for _, np := range slots {
		putNum(buf, np.h)
		putNum(buf[4:], np.pos)
		if _, err = w.Write(buf[:8]); err != nil {
			return
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main
import (
	"github.com/alexflint/go-arg"
	"fmt"
	"path/filepath"
	"os"
	"strings"
	"golang.org/x/crypto/ssh"
	"io/ioutil"
	"io"
	"encoding/json"
	"net/http"
	"github.com/gorilla/mux"
	"strconv"
	"bytes"
	"reflect"
)

type SSHClient struct {
	Config *ssh.ClientConfig
	Host   string
	Port   int
}

type SSHCommand struct {
	Path   string
	Env    []string
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}
var args struct {
	Env       string    `arg:"-e,help:choose an environment to scan,required"`
	Locations []string `arg:"-l,help:search location origin"`
	Repo      string    `arg:"-r,help:pick a single repo to check"`
	Debug     bool    `arg:"-d,help:turn on debug mode"`
	Host      string    `args:"-H,help:URI of host to interogate,required"`
	Port      int    `args:"-P,help:SSH port of host,required"`
	File      string    `args:"-f,help:file containing commands to execute,required"`
}

type Context struct {
	Env    string `json:"env"`
	System []struct {
		Commands []string `json:"commands"`
		DNS      string   `json:"dns"`
		Port     string   `json:"port"`
		Name     string   `json:"name"`
	} `json:"system"`
}

type BuildInfo struct {
	Build_date         string `json:"build.date"`
	Build_version_full string `json:"build.version.full"`
}

type Versions struct {
	Env string `json:"env"`
	Vrs []Version `json:"versions"`
}

type Version struct {
	System    string `json:"system"`
	Version   string  `json:"version"`
	BuildInfo BuildInfo `json:"buildInfo"`
}

func (vers *Versions) AddVersion(version Version) []Version {
	vers.Vrs = append(vers.Vrs, version)
	return vers.Vrs
}

func (vers *Versions) AddEnv(env string) string {
	vers.Env = env
	return vers.Env
}
func (vers *Version) AddBuildInfo(buildInfo BuildInfo) BuildInfo {
	vers.BuildInfo = buildInfo
	return vers.BuildInfo
}

func init() {
	arg.MustParse(&args)

}

type prov struct {
	prov string
}

func main() {

	r := mux.NewRouter()
	r.HandleFunc("/getAgg", AggVersions)
	http.Handle("/", r)

	// Create client config

	fmt.Println("Listening...")
	http.ListenAndServe(":3000", nil)

	fmt.Printf("The environment you picked was --> %s\n ", args.Env)

	for _, loc := range args.Locations {

		if err := filepath.Walk(loc, func(path string, fileInfo os.FileInfo, _ error) error {
			if !(strings.Contains(path, ".git")) {
				if fileInfo.Mode().IsRegular() {
					fmt.Printf("The file %s is a file %t \n", path, fileInfo.Mode().IsRegular())
				}
			}
			return nil
		}); err != nil {
			fmt.Printf("The path to enlightenment was fraught  .........%s ,\n", err)
			os.Exit(1)
		}

	}

}

func AggVersions(w http.ResponseWriter, r *http.Request) {

	b, err := json.Marshal(ComposeJSON())
	if err != nil {
		fmt.Println("Error Marshalling struct j:", err)
		return
	}
	w.Write([]byte(b))

}

func ComposeJSON() *Versions {

	sshCnf := getSshConfig()

	versArry := make([]Version, 2)

	v := new(Versions)

	file, err1 := os.Open("commands.json")
	if err1 != nil {
		fmt.Println("Error:", err1)
	} //#2
	defer file.Close()
	fmt.Println("Hello json")

	decoder := json.NewDecoder(file)
	context := Context{}
	err := decoder.Decode(&context)
	if err != nil {
		fmt.Println("Error:", err)
	}
	v.AddEnv(context.Env)

	for i, system := range context.System {

		fmt.Printf("For %d get system --> %s at %s", i, system.Name, system.DNS)

		for i, cmd := range system.Commands {

			client := getSshClient(sshCnf, system.DNS, system.Port)

			commd := getSshCommand(cmd)

			fmt.Printf("Running command: %s\n", commd.Path)

			if (strings.Contains(system.Name, "agg-private")) {

			}

			if err, prov := client.RunCommand(commd); err != nil {
				fmt.Fprintf(os.Stderr, "command run error: %s\n", err)
				os.Exit(1)
			} else {
				fmt.Println("reflect -> ",reflect.TypeOf(prov))
				if (strings.Contains(system.Name, "agg-private")) {
					decode := json.NewDecoder(bytes.NewReader([]uint8(prov.(string))))
					bi := BuildInfo{}
					err1 := decode.Decode(&bi)

					if err1 != nil {
						fmt.Println("Error: decoding build info", err1)
					} else {
						versArry[i] = Version{system.Name, "", bi}
						v.AddVersion(versArry[i])
					}

				} else {

					versArry[i] = Version{system.Name, prov.(string), BuildInfo{}}
					v.AddVersion(versArry[i])
				}
			}
		}
	}

	for _, vrs := range v.Vrs {

		fmt.Printf("Ranger\t\t\t-->%s<-- \n\t\t-->%s<-- \n", vrs.System, vrs.Version)
	}
	return v
}

func getSshClient(sshCnf *ssh.ClientConfig, host string, port string) *SSHClient {
	nPort, err := strconv.Atoi(port)
	if err != nil {
		fmt.Printf("Error: port number is invalid %s , %s", port, err)
	}
	client := &SSHClient{
		Config: sshCnf,
		Host:   host,
		Port:   nPort,
	}
	return client
}

func getSshCommand(commd string) *SSHCommand {

	cmd := &SSHCommand{
		Path:   commd,
		Env:    []string{""},
		Stdin:  os.Stdin,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}

	return cmd

}

func getSshConfig() *ssh.ClientConfig {

	sshConfig := &ssh.ClientConfig{
		User: "root",
		Auth: []ssh.AuthMethod{
			PublicKeyFile("/Users/adrianjackson/.ssh/tsmkey05.pem"),
		},
	}
	return sshConfig

}
func PublicKeyFile(file string) ssh.AuthMethod {
	buffer, err := ioutil.ReadFile(file)
	if err != nil {
		return nil
	}

	key, err := ssh.ParsePrivateKey(buffer)
	if err != nil {
		return nil
	}
	return ssh.PublicKeys(key)
}

func (client *SSHClient) RunCommand(cmd *SSHCommand) (error, interface{}) {
	rescueStdout := os.Stdout
	r, w, _ := os.Pipe()
	cmd.Stdout = w

	var (
		session *ssh.Session
		err error
	)

	if session, err = client.newSession(); err != nil {
		return err, nil
	}
	defer session.Close()

	if err = client.prepareCommand(session, cmd); err != nil {
		return err, nil
	}

	if err = session.Run(cmd.Path); err != nil {
		return err, nil
	}
	//err = session.Run(cmd.Path)
	//fmt.Printf("Err -> %s\n", err.Error())

	w.Close()

	out, _ := ioutil.ReadAll(r)
	os.Stdout = rescueStdout

	if (strings.Contains(cmd.Path, "providerLib")) {

		s1 := string([]byte(out))[(strings.Index(string([]byte(out)), ".jar") - 7):(strings.Index(string([]byte(out)), ".jar"))]

		return err, s1
	} else if (strings.Contains(cmd.Path, "build")) {

		s1 := string([]byte(out))
		fmt.Printf("build-->%s<--", string(s1))

		return err, s1
	}

	return err, nil
}

func (client *SSHClient) newSession() (*ssh.Session, error) {
	connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Host, client.Port), client.Config)
	if err != nil {
		return nil, fmt.Errorf("Failed to dial: %s", err)
	}

	session, err := connection.NewSession()
	if err != nil {
		return nil, fmt.Errorf("Failed to create session: %s", err)
	}

	modes := ssh.TerminalModes{
		// ssh.ECHO:          0,     // disable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}

	if err := session.RequestPty("xterm", 80, 80, modes); err != nil {
		session.Close()
		return nil, fmt.Errorf("request for pseudo terminal failed: %s", err)
	}

	return session, nil
}
func (client *SSHClient) prepareCommand(session *ssh.Session, cmd *SSHCommand) error {
	for _, env := range cmd.Env {
		variable := strings.Split(env, "=")
		if len(variable) != 2 {
			continue
		}

		if err := session.Setenv(variable[0], variable[1]); err != nil {
			return err
		}
	}

	if cmd.Stdin != nil {
		stdin, err := session.StdinPipe()
		if err != nil {
			return fmt.Errorf("Unable to setup stdin for session: %v", err)
		}
		go io.Copy(stdin, cmd.Stdin)
	}

	if cmd.Stdout != nil {
		stdout, err := session.StdoutPipe()
		if err != nil {
			return fmt.Errorf("Unable to setup stdout for session: %v", err)
		}

		go io.Copy(cmd.Stdout, stdout)
	}

	if cmd.Stderr != nil {
		stderr, err := session.StderrPipe()
		if err != nil {
			return fmt.Errorf("Unable to setup stderr for session: %v", err)
		}
		go io.Copy(cmd.Stderr, stderr)
	}

	return nil
}
<commit_msg>Added in extra channels<commit_after>package main
import (
	"github.com/alexflint/go-arg"
	"fmt"
	"path/filepath"
	"os"
	"strings"
	"golang.org/x/crypto/ssh"
	"io/ioutil"
	"io"
	"encoding/json"
	"net/http"
	"github.com/gorilla/mux"
	"strconv"
	"bytes"
)

type SSHClient struct {
	Config *ssh.ClientConfig
	Host   string
	Port   int
}

type SSHCommand struct {
	Path   string
	Env    []string
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}
var args struct {
	Env       string    `arg:"-e,help:choose an environment to scan,required"`
	Locations []string `arg:"-l,help:search location origin"`
	Repo      string    `arg:"-r,help:pick a single repo to check"`
	Debug     bool    `arg:"-d,help:turn on debug mode"`
	Host      string    `args:"-H,help:URI of host to interogate,required"`
	Port      int    `args:"-P,help:SSH port of host,required"`
	File      string    `args:"-f,help:file containing commands to execute,required"`
}

type Context struct {
	Env    string `json:"env"`
	System []struct {
		Commands []string `json:"commands"`
		DNS      string   `json:"dns"`
		Port     string   `json:"port"`
		Channel  string   `json:"channel"`
		Name     string   `json:"name"`
	} `json:"system"`
}

type BuildInfo struct {
	Build_date         string `json:"build.date"`
	Build_version_full string `json:"build.version.full"`
}

type Versions struct {
	Env string `json:"env"`
	Vrs []Version `json:"versions"`
}

type Version struct {
	System    string `json:"system"`
	Channel   string `json:"channel"`
	Version   []string  `json:"version"`
	BuildInfo BuildInfo `json:"buildInfo"`
}

func (vers *Versions) AddVersion(version Version) []Version {
	vers.Vrs = append(vers.Vrs, version)
	return vers.Vrs
}

func (vers *Versions) AddEnv(env string) string {
	vers.Env = env
	return vers.Env
}
func (ver *Version) AddVer(vers []string) []string {
	ver.Version = vers
	return ver.Version
}
func (vers *Version) AddBuildInfo(buildInfo BuildInfo) BuildInfo {
	vers.BuildInfo = buildInfo
	return vers.BuildInfo
}

func init() {
	arg.MustParse(&args)

}

type prov struct {
	prov string
}

func main() {

	r := mux.NewRouter()
	r.HandleFunc("/getAgg", AggVersions)
	http.Handle("/", r)

	// Create client config

	fmt.Println("Listening...")
	http.ListenAndServe(":3000", nil)

	fmt.Printf("The environment you picked was --> %s\n ", args.Env)

	for _, loc := range args.Locations {

		if err := filepath.Walk(loc, func(path string, fileInfo os.FileInfo, _ error) error {
			if !(strings.Contains(path, ".git")) {
				if fileInfo.Mode().IsRegular() {
					fmt.Printf("The file %s is a file %t \n", path, fileInfo.Mode().IsRegular())
				}
			}
			return nil
		}); err != nil {
			fmt.Printf("The path to enlightenment was fraught  .........%s ,\n", err)
			os.Exit(1)
		}

	}

}

func AggVersions(w http.ResponseWriter, r *http.Request) {

	b, err := json.Marshal(ComposeJSON())
	if err != nil {
		fmt.Println("Error Marshalling struct j:", err)
		return
	}
	w.Write([]byte(b))

}

func ComposeJSON() *Versions {

	sshCnf := getSshConfig()

	versArry := make([]Version, 2)

	v := new(Versions)

	file, err1 := os.Open("commands.json")
	if err1 != nil {
		fmt.Println("Error:", err1)
	} //#2
	defer file.Close()
	fmt.Println("Hello json")

	decoder := json.NewDecoder(file)
	context := Context{}
	err := decoder.Decode(&context)
	if err != nil {
		fmt.Println("Error:", err)
	}
	v.AddEnv(context.Env)

	for i, system := range context.System {

		fmt.Printf("For %d get system --> %s at %s", i, system.Name, system.DNS)

		for i, cmd := range system.Commands {

			client := getSshClient(sshCnf, system.DNS, system.Port)

			commd := getSshCommand(cmd)

			fmt.Printf("Running command: %s\n", commd.Path)

			if (strings.Contains(system.Name, "agg-private")) {

			}

			if err, prov := client.RunCommand(commd); err != nil {
				fmt.Fprintf(os.Stderr, "command run error: %s\n", err)
				os.Exit(1)
			} else {

				if (strings.Contains(system.Name, "agg-private")) {
					decode := json.NewDecoder(bytes.NewReader([]uint8(prov.(string))))
					bi := BuildInfo{}
					err1 := decode.Decode(&bi)

					if err1 != nil {
						fmt.Println("Error: decoding build info", err1)
					} else {
						versArry[i] = Version{system.Name, "",nil, bi}
						v.AddVersion(versArry[i])
					}

				} else {

					str1 := strings.Split(prov.(string), ".jar")
					for n, x := range str1 {
						str1[n] = strings.TrimSpace(x)
					}

					for i, y := range str1 {
						fmt.Printf("No %d --yy--> %s\n", i, y)
					}

					versArry[i] = Version{system.Name, system.Channel,str1, BuildInfo{}}
					v.AddVersion(versArry[i])
				}
			}
		}
	}

	for _, vrs := range v.Vrs {

		fmt.Printf("Ranger\t\t\t-->%s<-- \n\t\t-->%s<-- \n", vrs.System, vrs.Version)
	}
	return v
}

func getSshClient(sshCnf *ssh.ClientConfig, host string, port string) *SSHClient {
	nPort, err := strconv.Atoi(port)
	if err != nil {
		fmt.Printf("Error: port number is invalid %s , %s", port, err)
	}
	client := &SSHClient{
		Config: sshCnf,
		Host:   host,
		Port:   nPort,
	}
	return client
}

func getSshCommand(commd string) *SSHCommand {

	cmd := &SSHCommand{
		Path:   commd,
		Env:    []string{""},
		Stdin:  os.Stdin,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}

	return cmd

}

func getSshConfig() *ssh.ClientConfig {

	sshConfig := &ssh.ClientConfig{
		User: "root",
		Auth: []ssh.AuthMethod{
			PublicKeyFile("/Users/adrianjackson/.ssh/tsmkey05.pem"),
		},
	}
	return sshConfig

}
func PublicKeyFile(file string) ssh.AuthMethod {
	buffer, err := ioutil.ReadFile(file)
	if err != nil {
		return nil
	}

	key, err := ssh.ParsePrivateKey(buffer)
	if err != nil {
		return nil
	}
	return ssh.PublicKeys(key)
}

func (client *SSHClient) RunCommand(cmd *SSHCommand) (error, interface{}) {
	rescueStdout := os.Stdout
	r, w, _ := os.Pipe()
	cmd.Stdout = w

	var (
		session *ssh.Session
		err error
	)

	if session, err = client.newSession(); err != nil {
		return err, nil
	}
	defer session.Close()

	if err = client.prepareCommand(session, cmd); err != nil {
		return err, nil
	}

	if err = session.Run(cmd.Path); err != nil {
		return err, nil
	}
	//err = session.Run(cmd.Path)
	//fmt.Printf("Err -> %s\n", err.Error())

	w.Close()

	out, _ := ioutil.ReadAll(r)
	os.Stdout = rescueStdout

	if (strings.Contains(cmd.Path, "providerLib")) {

		fmt.Printf("Jars ->%s<--\n\n", string([]byte(out)))

		fmt.Printf("\n\nRedone jars -->%s<--", strings.Replace(string([]byte(out)), "\n", "", 9999))

		//		s1 := string([]byte(out))[(strings.Index(string([]byte(out)), ".jar") - 7):(strings.Index(string([]byte(out)), ".jar"))]

		s1 := string([]byte(out))

		return err, s1
	} else if (strings.Contains(cmd.Path, "build")) {

		s1 := string([]byte(out))
		fmt.Printf("build-->%s<--", string(s1))

		return err, s1
	}

	return err, nil
}

// two byte-oriented functions identical except for operator comparing c to 127.
func stripCtlFromBytes(str string) string {
	b := make([]byte, len(str))
	var bl int
	for i := 0; i < len(str); i++ {
		c := str[i]
		if c >= 32 && c != 127 {
			b[bl] = c
			bl++
		}
	}
	return string(b[:bl])
}

func (client *SSHClient) newSession() (*ssh.Session, error) {
	connection, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Host, client.Port), client.Config)
	if err != nil {
		return nil, fmt.Errorf("Failed to dial: %s", err)
	}

	session, err := connection.NewSession()
	if err != nil {
		return nil, fmt.Errorf("Failed to create session: %s", err)
	}

	modes := ssh.TerminalModes{
		// ssh.ECHO:          0,     // disable echoing
		ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
		ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
	}

	if err := session.RequestPty("xterm", 80, 80, modes); err != nil {
		session.Close()
		return nil, fmt.Errorf("request for pseudo terminal failed: %s", err)
	}

	return session, nil
}
func (client *SSHClient) prepareCommand(session *ssh.Session, cmd *SSHCommand) error {
	for _, env := range cmd.Env {
		variable := strings.Split(env, "=")
		if len(variable) != 2 {
			continue
		}

		if err := session.Setenv(variable[0], variable[1]); err != nil {
			return err
		}
	}

	if cmd.Stdin != nil {
		stdin, err := session.StdinPipe()
		if err != nil {
			return fmt.Errorf("Unable to setup stdin for session: %v", err)
		}
		go io.Copy(stdin, cmd.Stdin)
	}

	if cmd.Stdout != nil {
		stdout, err := session.StdoutPipe()
		if err != nil {
			return fmt.Errorf("Unable to setup stdout for session: %v", err)
		}

		go io.Copy(cmd.Stdout, stdout)
	}

	if cmd.Stderr != nil {
		stderr, err := session.StderrPipe()
		if err != nil {
			return fmt.Errorf("Unable to setup stderr for session: %v", err)
		}
		go io.Copy(cmd.Stderr, stderr)
	}

	return nil
}
<|endoftext|>


<commit_before>// +build acceptance

package v1

import (
	"os"
	"testing"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack"
	th "github.com/rackspace/gophercloud/testhelper"
)

const template = `
{
		"heat_template_version": "2013-05-23",
		"description": "Simple template to test heat commands",
		"parameters": {
				"flavor": {
						"default": "m1.tiny",
						"type": "string"
				}
		},
		"resources": {
				"hello_world": {
						"type":"OS::Nova::Server",
						"properties": {
								"key_name": "heat_key",
								"flavor": {
										"get_param": "flavor"
								},
								"image": "ad091b52-742f-469e-8f3c-fd81cadf0743",
								"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
						}
				}
		}
}
`

func newClient(t *testing.T) *gophercloud.ServiceClient {
	ao, err := openstack.AuthOptionsFromEnv()
	th.AssertNoErr(t, err)

	client, err := openstack.AuthenticatedClient(ao)
	th.AssertNoErr(t, err)

	c, err := openstack.NewOrchestrationV1(client, gophercloud.EndpointOpts{
		Region: os.Getenv("OS_REGION_NAME"),
	})
	th.AssertNoErr(t, err)
	return c
}
<commit_msg>make openstack acceptance test template use env vars<commit_after>// +build acceptance

package v1

import (
	"fmt"
	"os"
	"testing"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack"
	th "github.com/rackspace/gophercloud/testhelper"
)

var template = fmt.Sprintf(`
{
	"heat_template_version": "2013-05-23",
	"description": "Simple template to test heat commands",
	"parameters": {},
	"resources": {
		"hello_world": {
			"type":"OS::Nova::Server",
			"properties": {
				"flavor": "%s",
				"image": "%s",
				"user_data": "#!/bin/bash -xv\necho \"hello world\" > /root/hello-world.txt\n"
			}
		}
	}
}`, os.Getenv("OS_FLAVOR_ID"), os.Getenv("OS_IMAGE_ID"))

func newClient(t *testing.T) *gophercloud.ServiceClient {
	ao, err := openstack.AuthOptionsFromEnv()
	th.AssertNoErr(t, err)

	client, err := openstack.AuthenticatedClient(ao)
	th.AssertNoErr(t, err)

	c, err := openstack.NewOrchestrationV1(client, gophercloud.EndpointOpts{
		Region: os.Getenv("OS_REGION_NAME"),
	})
	th.AssertNoErr(t, err)
	return c
}
<|endoftext|>


<commit_before>package jsongo

import (
	"testing"
	"encoding/json"
	"reflect"
	"log"
)

func Test_create_empty_object(t *testing.T) {
	expect := bytes2json([]byte(`{}`))
	result := Object()
	check(t, expect, result)
}

func Test_create_populated_object(t *testing.T) {
	expect := bytes2json([]byte(`{"name":"Ricardo Longa","idade":28,"skills":["Golang","Android"]}`))
	result := Object().Put("name", "Ricardo Longa").Put("idade", 28).Put("skills", Array().Put("Golang").Put("Android"))
	check(t, expect, result)
}

func Test_duas_strings(t *testing.T) {
	expect := "{\"name\":\"Ricardo Longa\",\"idade\":28,\"skills\":[\"Golang\",\"Android\"]}"
	result := "{\"skills\":[\"Android\",\"Golang\"]},\"idade\":28,\"name\":\"Ricardo Longa\"}"
	check(t, expect, result)
}

func bytes2json(data []byte) (map[string]interface{}) {
	var jsonData interface{}

	err := json.Unmarshal(data, &jsonData)

	if err != nil {
		return nil
	}

	return jsonData.(map[string]interface{})
}

func check(t *testing.T, expect, result interface{}) {
	log.Printf("Expect: %s\n", expect)
	log.Printf("Result: %s\n", result)

	if !reflect.DeepEqual(expect, result) {
		t.Errorf("\n\nExpect: %s\nResult: %s", expect, result)
	}

	log.Printf("")
}<commit_msg>corrigido tests.<commit_after>package jsongo

import (
	"testing"
	"encoding/json"
	"reflect"
	"log"
)

func Test_create_empty_object(t *testing.T) {
	expect := bytes2json([]byte(`{}`))
	result := Object()

	check(t, struct2json(expect), struct2json(result))
}

func Test_create_populated_object(t *testing.T) {
	expect := bytes2json([]byte(`{"name":"Ricardo Longa","idade":28,"skills":["Golang","Android"]}`))
	result := Object().Put("name", "Ricardo Longa").Put("idade", 28).Put("skills", Array().Put("Golang").Put("Android"))

	check(t, struct2json(expect), struct2json(result))
}

func bytes2json(data []byte) map[string]interface{} {
	var jsonData interface{}

	err := json.Unmarshal(data, &jsonData)

	if err != nil {
		return nil
	}

	return jsonData.(map[string]interface{})
}

func struct2json(data interface{}) string {
	byteArray, err := json.Marshal(data)
	//	byteArray, err = json.MarshalIndent(pessoa, "", "    ")

	if err != nil {
		return ""
	}

	return string(byteArray)
}

func check(t *testing.T, expect, result interface{}) {
	if !reflect.DeepEqual(expect, result) {
		t.Errorf("\n\nExpect: %s\nResult: %s", expect, result)
	}

	log.Printf("")
}<|endoftext|>


<commit_before>// Package junit defines a JUnit XML report and includes convenience methods
// for working with these reports.
package junit

import (
	"encoding/xml"
	"fmt"
	"strings"
	"time"

	"github.com/jstemmer/go-junit-report/v2/gtr"
)

// Testsuites is a collection of JUnit testsuites.
type Testsuites struct {
	XMLName xml.Name `xml:"testsuites"`

	Name     string `xml:"name,attr,omitempty"`
	Time     string `xml:"time,attr,omitempty"` // total duration in seconds
	Tests    int    `xml:"tests,attr,omitempty"`
	Errors   int    `xml:"errors,attr,omitempty"`
	Failures int    `xml:"failures,attr,omitempty"`
	Skipped  int    `xml:"skipped,attr,omitempty"`
	Disabled int    `xml:"disabled,attr,omitempty"`

	Suites []Testsuite `xml:"testsuite,omitempty"`
}

// AddSuite adds a Testsuite and updates this testssuites' totals.
func (t *Testsuites) AddSuite(ts Testsuite) {
	t.Suites = append(t.Suites, ts)
	t.Tests += ts.Tests
	t.Errors += ts.Errors
	t.Failures += ts.Failures
	t.Skipped += ts.Skipped
	t.Disabled += ts.Disabled
}

// Testsuite is a single JUnit testsuite containing testcases.
type Testsuite struct {
	// required attributes
	Name     string `xml:"name,attr"`
	Tests    int    `xml:"tests,attr"`
	Failures int    `xml:"failures,attr"`
	Errors   int    `xml:"errors,attr"`
	ID       int    `xml:"id,attr"`

	// optional attributes
	Disabled  int    `xml:"disabled,attr,omitempty"`
	Hostname  string `xml:"hostname,attr,omitempty"`
	Package   string `xml:"package,attr,omitempty"`
	Skipped   int    `xml:"skipped,attr,omitempty"`
	Time      string `xml:"time,attr"`                // duration in seconds
	Timestamp string `xml:"timestamp,attr,omitempty"` // date and time in ISO8601
	File      string `xml:"file,attr"`

	Properties *[]Property `xml:"properties>property,omitempty"`
	Testcases  []Testcase  `xml:"testcase,omitempty"`
	SystemOut  *Output     `xml:"system-out,omitempty"`
	SystemErr  *Output     `xml:"system-err,omitempty"`
}

// AddProperty adds a property with the given name and value to this Testsuite.
func (t *Testsuite) AddProperty(name, value string) {
	prop := Property{Name: name, Value: value}
	if t.Properties == nil {
		t.Properties = &[]Property{prop}
		return
	}
	props := append(*t.Properties, prop)
	t.Properties = &props
}

// AddTestcase adds Testcase tc to this Testsuite.
func (t *Testsuite) AddTestcase(tc Testcase) {
	t.Testcases = append(t.Testcases, tc)
	t.Tests += 1

	if tc.Error != nil {
		t.Errors += 1
	}

	if tc.Failure != nil {
		t.Failures += 1
	}

	if tc.Skipped != nil {
		t.Skipped += 1
	}
}

// SetTimestamp sets the timestamp in this Testsuite.
func (ts *Testsuite) SetTimestamp(t time.Time) {
	ts.Timestamp = t.Format(time.RFC3339)
}

// Testcase represents a single test with its results.
type Testcase struct {
	// required attributes
	Name      string `xml:"name,attr"`
	Classname string `xml:"classname,attr"`

	// optional attributes
	Time   string `xml:"time,attr,omitempty"` // duration in seconds
	Status string `xml:"status,attr,omitempty"`

	Skipped   *Result `xml:"skipped,omitempty"`
	Error     *Result `xml:"error,omitempty"`
	Failure   *Result `xml:"failure,omitempty"`
	SystemOut *Output `xml:"system-out,omitempty"`
	SystemErr *Output `xml:"system-err,omitempty"`
}

// Property represents a key/value pair.
type Property struct {
	Name  string `xml:"name,attr"`
	Value string `xml:"value,attr"`
}

// Result represents the result of a single test.
type Result struct {
	Message string `xml:"message,attr"`
	Type    string `xml:"type,attr,omitempty"`
	Data    string `xml:",cdata"`
}

// Output represents output written to stdout or sderr.
type Output struct {
	Data string `xml:",cdata"`
}

// CreateFromReport creates a JUnit representation of the given gtr.Report.
func CreateFromReport(report gtr.Report, hostname string) Testsuites {
	var suites Testsuites
	for _, pkg := range report.Packages {
		var duration time.Duration
		suite := Testsuite{
			Name:     pkg.Name,
			Hostname: hostname,
			ID:       len(suites.Suites),
		}

		if !pkg.Timestamp.IsZero() {
			suite.SetTimestamp(pkg.Timestamp)
		}

		for k, v := range pkg.Properties {
			suite.AddProperty(k, v)
		}

		if len(pkg.Output) > 0 {
			suite.SystemOut = &Output{Data: formatOutput(pkg.Output, 0)}
		}

		if pkg.Coverage > 0 {
			suite.AddProperty("coverage.statements.pct", fmt.Sprintf("%.2f", pkg.Coverage))
		}

		for _, test := range pkg.Tests {
			duration += test.Duration
			suite.AddTestcase(createTestcaseForTest(pkg.Name, test))
		}

		// JUnit doesn't have a good way of dealing with build or runtime
		// errors that happen before a test has started, so we create a single
		// failing test that contains the build error details.
		if pkg.BuildError.Name != "" {
			tc := Testcase{
				Classname: pkg.BuildError.Name,
				Name:      pkg.BuildError.Cause,
				Time:      formatDuration(0),
				Error: &Result{
					Message: "Build error",
					Data:    strings.Join(pkg.BuildError.Output, "\n"),
				},
			}
			suite.AddTestcase(tc)
		}

		if pkg.RunError.Name != "" {
			tc := Testcase{
				Classname: pkg.RunError.Name,
				Name:      "Failure",
				Time:      formatDuration(0),
				Error: &Result{
					Message: "Runtime error",
					Data:    strings.Join(pkg.RunError.Output, "\n"),
				},
			}
			suite.AddTestcase(tc)
		}

		if (pkg.Duration) == 0 {
			suite.Time = formatDuration(duration)
		} else {
			suite.Time = formatDuration(pkg.Duration)
		}
		suites.AddSuite(suite)
	}
	return suites
}

func createTestcaseForTest(pkgName string, test gtr.Test) Testcase {
	tc := Testcase{
		Classname: pkgName,
		Name:      test.Name,
		Time:      formatDuration(test.Duration),
	}

	if test.Result == gtr.Fail {
		tc.Failure = &Result{
			Message: "Failed",
			Data:    formatOutput(test.Output, test.Level),
		}
	} else if test.Result == gtr.Skip {
		tc.Skipped = &Result{
			Message: "Skipped",
			Data:    formatOutput(test.Output, test.Level),
		}
	} else if test.Result == gtr.Unknown {
		tc.Error = &Result{
			Message: "No test result found",
			Data:    formatOutput(test.Output, test.Level),
		}
	} else if len(test.Output) > 0 {
		tc.SystemOut = &Output{Data: formatOutput(test.Output, test.Level)}
	}
	return tc
}

// formatDuration returns the JUnit string representation of the given
// duration.
func formatDuration(d time.Duration) string {
	return fmt.Sprintf("%.3f", d.Seconds())
}

// formatOutput combines the lines from the given output into a single string.
func formatOutput(output []string, indent int) string {
	return strings.Join(output, "\n")
}
<commit_msg>junit: Don't include File attribute when it's empty<commit_after>// Package junit defines a JUnit XML report and includes convenience methods
// for working with these reports.
package junit

import (
	"encoding/xml"
	"fmt"
	"strings"
	"time"

	"github.com/jstemmer/go-junit-report/v2/gtr"
)

// Testsuites is a collection of JUnit testsuites.
type Testsuites struct {
	XMLName xml.Name `xml:"testsuites"`

	Name     string `xml:"name,attr,omitempty"`
	Time     string `xml:"time,attr,omitempty"` // total duration in seconds
	Tests    int    `xml:"tests,attr,omitempty"`
	Errors   int    `xml:"errors,attr,omitempty"`
	Failures int    `xml:"failures,attr,omitempty"`
	Skipped  int    `xml:"skipped,attr,omitempty"`
	Disabled int    `xml:"disabled,attr,omitempty"`

	Suites []Testsuite `xml:"testsuite,omitempty"`
}

// AddSuite adds a Testsuite and updates this testssuites' totals.
func (t *Testsuites) AddSuite(ts Testsuite) {
	t.Suites = append(t.Suites, ts)
	t.Tests += ts.Tests
	t.Errors += ts.Errors
	t.Failures += ts.Failures
	t.Skipped += ts.Skipped
	t.Disabled += ts.Disabled
}

// Testsuite is a single JUnit testsuite containing testcases.
type Testsuite struct {
	// required attributes
	Name     string `xml:"name,attr"`
	Tests    int    `xml:"tests,attr"`
	Failures int    `xml:"failures,attr"`
	Errors   int    `xml:"errors,attr"`
	ID       int    `xml:"id,attr"`

	// optional attributes
	Disabled  int    `xml:"disabled,attr,omitempty"`
	Hostname  string `xml:"hostname,attr,omitempty"`
	Package   string `xml:"package,attr,omitempty"`
	Skipped   int    `xml:"skipped,attr,omitempty"`
	Time      string `xml:"time,attr"`                // duration in seconds
	Timestamp string `xml:"timestamp,attr,omitempty"` // date and time in ISO8601
	File      string `xml:"file,attr,omitempty"`

	Properties *[]Property `xml:"properties>property,omitempty"`
	Testcases  []Testcase  `xml:"testcase,omitempty"`
	SystemOut  *Output     `xml:"system-out,omitempty"`
	SystemErr  *Output     `xml:"system-err,omitempty"`
}

// AddProperty adds a property with the given name and value to this Testsuite.
func (t *Testsuite) AddProperty(name, value string) {
	prop := Property{Name: name, Value: value}
	if t.Properties == nil {
		t.Properties = &[]Property{prop}
		return
	}
	props := append(*t.Properties, prop)
	t.Properties = &props
}

// AddTestcase adds Testcase tc to this Testsuite.
func (t *Testsuite) AddTestcase(tc Testcase) {
	t.Testcases = append(t.Testcases, tc)
	t.Tests += 1

	if tc.Error != nil {
		t.Errors += 1
	}

	if tc.Failure != nil {
		t.Failures += 1
	}

	if tc.Skipped != nil {
		t.Skipped += 1
	}
}

// SetTimestamp sets the timestamp in this Testsuite.
func (ts *Testsuite) SetTimestamp(t time.Time) {
	ts.Timestamp = t.Format(time.RFC3339)
}

// Testcase represents a single test with its results.
type Testcase struct {
	// required attributes
	Name      string `xml:"name,attr"`
	Classname string `xml:"classname,attr"`

	// optional attributes
	Time   string `xml:"time,attr,omitempty"` // duration in seconds
	Status string `xml:"status,attr,omitempty"`

	Skipped   *Result `xml:"skipped,omitempty"`
	Error     *Result `xml:"error,omitempty"`
	Failure   *Result `xml:"failure,omitempty"`
	SystemOut *Output `xml:"system-out,omitempty"`
	SystemErr *Output `xml:"system-err,omitempty"`
}

// Property represents a key/value pair.
type Property struct {
	Name  string `xml:"name,attr"`
	Value string `xml:"value,attr"`
}

// Result represents the result of a single test.
type Result struct {
	Message string `xml:"message,attr"`
	Type    string `xml:"type,attr,omitempty"`
	Data    string `xml:",cdata"`
}

// Output represents output written to stdout or sderr.
type Output struct {
	Data string `xml:",cdata"`
}

// CreateFromReport creates a JUnit representation of the given gtr.Report.
func CreateFromReport(report gtr.Report, hostname string) Testsuites {
	var suites Testsuites
	for _, pkg := range report.Packages {
		var duration time.Duration
		suite := Testsuite{
			Name:     pkg.Name,
			Hostname: hostname,
			ID:       len(suites.Suites),
		}

		if !pkg.Timestamp.IsZero() {
			suite.SetTimestamp(pkg.Timestamp)
		}

		for k, v := range pkg.Properties {
			suite.AddProperty(k, v)
		}

		if len(pkg.Output) > 0 {
			suite.SystemOut = &Output{Data: formatOutput(pkg.Output, 0)}
		}

		if pkg.Coverage > 0 {
			suite.AddProperty("coverage.statements.pct", fmt.Sprintf("%.2f", pkg.Coverage))
		}

		for _, test := range pkg.Tests {
			duration += test.Duration
			suite.AddTestcase(createTestcaseForTest(pkg.Name, test))
		}

		// JUnit doesn't have a good way of dealing with build or runtime
		// errors that happen before a test has started, so we create a single
		// failing test that contains the build error details.
		if pkg.BuildError.Name != "" {
			tc := Testcase{
				Classname: pkg.BuildError.Name,
				Name:      pkg.BuildError.Cause,
				Time:      formatDuration(0),
				Error: &Result{
					Message: "Build error",
					Data:    strings.Join(pkg.BuildError.Output, "\n"),
				},
			}
			suite.AddTestcase(tc)
		}

		if pkg.RunError.Name != "" {
			tc := Testcase{
				Classname: pkg.RunError.Name,
				Name:      "Failure",
				Time:      formatDuration(0),
				Error: &Result{
					Message: "Runtime error",
					Data:    strings.Join(pkg.RunError.Output, "\n"),
				},
			}
			suite.AddTestcase(tc)
		}

		if (pkg.Duration) == 0 {
			suite.Time = formatDuration(duration)
		} else {
			suite.Time = formatDuration(pkg.Duration)
		}
		suites.AddSuite(suite)
	}
	return suites
}

func createTestcaseForTest(pkgName string, test gtr.Test) Testcase {
	tc := Testcase{
		Classname: pkgName,
		Name:      test.Name,
		Time:      formatDuration(test.Duration),
	}

	if test.Result == gtr.Fail {
		tc.Failure = &Result{
			Message: "Failed",
			Data:    formatOutput(test.Output, test.Level),
		}
	} else if test.Result == gtr.Skip {
		tc.Skipped = &Result{
			Message: "Skipped",
			Data:    formatOutput(test.Output, test.Level),
		}
	} else if test.Result == gtr.Unknown {
		tc.Error = &Result{
			Message: "No test result found",
			Data:    formatOutput(test.Output, test.Level),
		}
	} else if len(test.Output) > 0 {
		tc.SystemOut = &Output{Data: formatOutput(test.Output, test.Level)}
	}
	return tc
}

// formatDuration returns the JUnit string representation of the given
// duration.
func formatDuration(d time.Duration) string {
	return fmt.Sprintf("%.3f", d.Seconds())
}

// formatOutput combines the lines from the given output into a single string.
func formatOutput(output []string, indent int) string {
	return strings.Join(output, "\n")
}
<|endoftext|>