<commit_before>// Copyright 2016 Hajime Hoshi
//
// 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.

//go:build (android || ios) && !ebitencbackend
// +build android ios
// +build !ebitencbackend

package ui

import (
	"fmt"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"unicode"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
	"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
	"github.com/hajimehoshi/ebiten/v2/internal/restorable"
	"github.com/hajimehoshi/ebiten/v2/internal/thread"
)

var (
	glContextCh = make(chan gl.Context, 1)

	// renderCh receives when updating starts.
	renderCh = make(chan struct{})

	// renderEndCh receives when updating finishes.
	renderEndCh = make(chan struct{})
)

func init() {
	theUI.userInterfaceImpl = userInterfaceImpl{
		foreground: 1,
		errCh:      make(chan error),

		// Give a default outside size so that the game can start without initializing them.
		outsideWidth:  640,
		outsideHeight: 480,
	}
	theUI.input.ui = &theUI.userInterfaceImpl
}

// Update is called from mobile/ebitenmobileview.
//
// Update must be called on the rendering thread.
func (u *userInterfaceImpl) Update() error {
	select {
	case err := <-u.errCh:
		return err
	default:
	}

	if !u.IsFocused() {
		return nil
	}

	gamepad.Update()

	renderCh <- struct{}{}
	go func() {
		<-renderEndCh
		u.t.Stop()
	}()
	u.t.Loop()
	return nil
}

type userInterfaceImpl struct {
	graphicsDriver graphicsdriver.Graphics

	outsideWidth  float64
	outsideHeight float64

	foreground int32
	errCh      chan error

	// Used for gomobile-build
	gbuildWidthPx   int
	gbuildHeightPx  int
	setGBuildSizeCh chan struct{}
	once            sync.Once

	context *contextImpl

	input Input

	fpsMode         FPSModeType
	renderRequester RenderRequester

	t *thread.OSThread

	m sync.RWMutex
}

func deviceScale() float64 {
	return devicescale.GetAt(0, 0)
}

// appMain is the main routine for gomobile-build mode.
func (u *userInterfaceImpl) appMain(a app.App) {
	var glctx gl.Context
	var sizeInited bool

	touches := map[touch.Sequence]Touch{}
	keys := map[Key]struct{}{}

	for e := range a.Events() {
		var updateInput bool
		var runes []rune

		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				if err := u.SetForeground(true); err != nil {
					// There are no other ways than panicking here.
					panic(err)
				}
				restorable.OnContextLost()
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				if err := u.SetForeground(false); err != nil {
					// There are no other ways than panicking here.
					panic(err)
				}
				glctx = nil
			}
		case size.Event:
			u.setGBuildSize(e.WidthPx, e.HeightPx)
			sizeInited = true
		case paint.Event:
			if !sizeInited {
				a.Send(paint.Event{})
				continue
			}
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderEndCh
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			if !sizeInited {
				continue
			}
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := deviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				touches[e.Sequence] = Touch{
					ID: TouchID(e.Sequence),
					X:  int(x),
					Y:  int(y),
				}
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			updateInput = true
		case key.Event:
			k, ok := gbuildKeyToUIKey[e.Code]
			if ok {
				switch e.Direction {
				case key.DirPress, key.DirNone:
					keys[k] = struct{}{}
				case key.DirRelease:
					delete(keys, k)
				}
			}

			switch e.Direction {
			case key.DirPress, key.DirNone:
				if e.Rune != -1 && unicode.IsPrint(e.Rune) {
					runes = []rune{e.Rune}
				}
			}
			updateInput = true
		}

		if updateInput {
			var ts []Touch
			for _, t := range touches {
				ts = append(ts, t)
			}
			u.input.update(keys, runes, ts)
		}
	}
}

func (u *userInterfaceImpl) SetForeground(foreground bool) error {
	var v int32
	if foreground {
		v = 1
	}
	atomic.StoreInt32(&u.foreground, v)

	if foreground {
		return hooks.ResumeAudio()
	} else {
		return hooks.SuspendAudio()
	}
}

func (u *userInterfaceImpl) Run(game Game) error {
	u.setGBuildSizeCh = make(chan struct{})
	go func() {
		if err := u.run(game, true); err != nil {
			// As mobile apps never ends, Loop can't return. Just panic here.
			panic(err)
		}
	}()
	app.Main(u.appMain)
	return nil
}

func RunWithoutMainLoop(game Game) {
	theUI.runWithoutMainLoop(game)
}

func (u *userInterfaceImpl) runWithoutMainLoop(game Game) {
	go func() {
		if err := u.run(game, false); err != nil {
			u.errCh <- err
		}
	}()
}

func (u *userInterfaceImpl) run(game Game, mainloop bool) (err error) {
	// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
	// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
	// unfortunately.
	// TODO: Panic on other goroutines cannot be handled here.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v\n%s", r, string(debug.Stack()))
		}
	}()

	u.context = newContextImpl(game)
	u.graphicsDriver = graphicsDriver()

	if mainloop {
		// When mainloop is true, gomobile-build is used. In this case, GL functions must be called via
		// gl.Context so that they are called on the appropriate thread.
		ctx := <-glContextCh
		u.graphicsDriver.(*opengl.Graphics).SetGomobileGLContext(ctx)
	} else {
		u.t = thread.NewOSThread()
		graphicscommand.SetRenderingThread(u.t)
	}

	// If gomobile-build is used, wait for the outside size fixed.
	if u.setGBuildSizeCh != nil {
		<-u.setGBuildSizeCh
	}

	for {
		if err := u.update(); err != nil {
			return err
		}
	}
}

// outsideSize must be called on the same goroutine as update().
func (u *userInterfaceImpl) outsideSize() (float64, float64) {
	var outsideWidth, outsideHeight float64

	u.m.RLock()
	if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
		outsideWidth = u.outsideWidth
		outsideHeight = u.outsideHeight
	} else {
		// gomobile build
		d := deviceScale()
		outsideWidth = float64(u.gbuildWidthPx) / d
		outsideHeight = float64(u.gbuildHeightPx) / d
	}
	u.m.RUnlock()

	return outsideWidth, outsideHeight
}

func (u *userInterfaceImpl) update() error {
	<-renderCh
	defer func() {
		renderEndCh <- struct{}{}
	}()

	w, h := u.outsideSize()
	if err := u.context.updateFrame(u.graphicsDriver, w, h, deviceScale()); err != nil {
		return err
	}
	return nil
}

func (u *userInterfaceImpl) ScreenSizeInFullscreen() (int, int) {
	// TODO: This function should return gbuildWidthPx, gbuildHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

// SetOutsideSize is called from mobile/ebitenmobileview.
//
// SetOutsideSize is concurrent safe.
func (u *userInterfaceImpl) SetOutsideSize(outsideWidth, outsideHeight float64) {
	u.m.Lock()
	if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
		u.outsideWidth = outsideWidth
		u.outsideHeight = outsideHeight
	}
	u.m.Unlock()
}

func (u *userInterfaceImpl) setGBuildSize(widthPx, heightPx int) {
	u.m.Lock()
	u.gbuildWidthPx = widthPx
	u.gbuildHeightPx = heightPx
	u.m.Unlock()

	u.once.Do(func() {
		close(u.setGBuildSizeCh)
	})
}

func (u *userInterfaceImpl) adjustPosition(x, y int) (int, int) {
	xf, yf := u.context.adjustPosition(float64(x), float64(y), deviceScale())
	return int(xf), int(yf)
}

func (u *userInterfaceImpl) CursorMode() CursorMode {
	return CursorModeHidden
}

func (u *userInterfaceImpl) SetCursorMode(mode CursorMode) {
	// Do nothing
}

func (u *userInterfaceImpl) CursorShape() CursorShape {
	return CursorShapeDefault
}

func (u *userInterfaceImpl) SetCursorShape(shape CursorShape) {
	// Do nothing
}

func (u *userInterfaceImpl) IsFullscreen() bool {
	return false
}

func (u *userInterfaceImpl) SetFullscreen(fullscreen bool) {
	// Do nothing
}

func (u *userInterfaceImpl) IsFocused() bool {
	return atomic.LoadInt32(&u.foreground) != 0
}

func (u *userInterfaceImpl) IsRunnableOnUnfocused() bool {
	return false
}

func (u *userInterfaceImpl) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
	// Do nothing
}

func (u *userInterfaceImpl) SetFPSMode(mode FPSModeType) {
	u.fpsMode = mode
	u.updateExplicitRenderingModeIfNeeded()
}

func (u *userInterfaceImpl) updateExplicitRenderingModeIfNeeded() {
	if u.renderRequester == nil {
		return
	}
	u.renderRequester.SetExplicitRenderingMode(u.fpsMode == FPSModeVsyncOffMinimum)
}

func (u *userInterfaceImpl) DeviceScaleFactor() float64 {
	return deviceScale()
}

func (u *userInterfaceImpl) SetScreenTransparent(transparent bool) {
	// Do nothing
}

func (u *userInterfaceImpl) IsScreenTransparent() bool {
	return false
}

func (u *userInterfaceImpl) resetForTick() {
	u.input.resetForTick()
}

func (u *userInterfaceImpl) SetInitFocused(focused bool) {
	// Do nothing
}

func (u *userInterfaceImpl) Input() *Input {
	return &u.input
}

func (u *userInterfaceImpl) Window() *Window {
	return &Window{}
}

type Touch struct {
	ID TouchID
	X  int
	Y  int
}

func (u *userInterfaceImpl) UpdateInput(keys map[Key]struct{}, runes []rune, touches []Touch) {
	u.input.update(keys, runes, touches)
	if u.fpsMode == FPSModeVsyncOffMinimum {
		u.renderRequester.RequestRenderIfNeeded()
	}
}

type RenderRequester interface {
	SetExplicitRenderingMode(explicitRendering bool)
	RequestRenderIfNeeded()
}

func (u *userInterfaceImpl) SetRenderRequester(renderRequester RenderRequester) {
	u.renderRequester = renderRequester
	u.updateExplicitRenderingModeIfNeeded()
}

func (u *userInterfaceImpl) ScheduleFrame() {
	if u.renderRequester != nil && u.fpsMode == FPSModeVsyncOffMinimum {
		u.renderRequester.RequestRenderIfNeeded()
	}
}
<commit_msg>internal/ui: bug fix: OpenGL must be used when gomobile-build is used<commit_after>// Copyright 2016 Hajime Hoshi
//
// 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.

//go:build (android || ios) && !ebitencbackend
// +build android ios
// +build !ebitencbackend

package ui

import (
	"fmt"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"unicode"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/v2/internal/devicescale"
	"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicscommand"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
	"github.com/hajimehoshi/ebiten/v2/internal/restorable"
	"github.com/hajimehoshi/ebiten/v2/internal/thread"
)

var (
	glContextCh = make(chan gl.Context, 1)

	// renderCh receives when updating starts.
	renderCh = make(chan struct{})

	// renderEndCh receives when updating finishes.
	renderEndCh = make(chan struct{})
)

func init() {
	theUI.userInterfaceImpl = userInterfaceImpl{
		foreground: 1,
		errCh:      make(chan error),

		// Give a default outside size so that the game can start without initializing them.
		outsideWidth:  640,
		outsideHeight: 480,
	}
	theUI.input.ui = &theUI.userInterfaceImpl
}

// Update is called from mobile/ebitenmobileview.
//
// Update must be called on the rendering thread.
func (u *userInterfaceImpl) Update() error {
	select {
	case err := <-u.errCh:
		return err
	default:
	}

	if !u.IsFocused() {
		return nil
	}

	gamepad.Update()

	renderCh <- struct{}{}
	go func() {
		<-renderEndCh
		u.t.Stop()
	}()
	u.t.Loop()
	return nil
}

type userInterfaceImpl struct {
	graphicsDriver graphicsdriver.Graphics

	outsideWidth  float64
	outsideHeight float64

	foreground int32
	errCh      chan error

	// Used for gomobile-build
	gbuildWidthPx   int
	gbuildHeightPx  int
	setGBuildSizeCh chan struct{}
	once            sync.Once

	context *contextImpl

	input Input

	fpsMode         FPSModeType
	renderRequester RenderRequester

	t *thread.OSThread

	m sync.RWMutex
}

func deviceScale() float64 {
	return devicescale.GetAt(0, 0)
}

// appMain is the main routine for gomobile-build mode.
func (u *userInterfaceImpl) appMain(a app.App) {
	var glctx gl.Context
	var sizeInited bool

	touches := map[touch.Sequence]Touch{}
	keys := map[Key]struct{}{}

	for e := range a.Events() {
		var updateInput bool
		var runes []rune

		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				if err := u.SetForeground(true); err != nil {
					// There are no other ways than panicking here.
					panic(err)
				}
				restorable.OnContextLost()
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				if err := u.SetForeground(false); err != nil {
					// There are no other ways than panicking here.
					panic(err)
				}
				glctx = nil
			}
		case size.Event:
			u.setGBuildSize(e.WidthPx, e.HeightPx)
			sizeInited = true
		case paint.Event:
			if !sizeInited {
				a.Send(paint.Event{})
				continue
			}
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderEndCh
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			if !sizeInited {
				continue
			}
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := deviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				touches[e.Sequence] = Touch{
					ID: TouchID(e.Sequence),
					X:  int(x),
					Y:  int(y),
				}
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			updateInput = true
		case key.Event:
			k, ok := gbuildKeyToUIKey[e.Code]
			if ok {
				switch e.Direction {
				case key.DirPress, key.DirNone:
					keys[k] = struct{}{}
				case key.DirRelease:
					delete(keys, k)
				}
			}

			switch e.Direction {
			case key.DirPress, key.DirNone:
				if e.Rune != -1 && unicode.IsPrint(e.Rune) {
					runes = []rune{e.Rune}
				}
			}
			updateInput = true
		}

		if updateInput {
			var ts []Touch
			for _, t := range touches {
				ts = append(ts, t)
			}
			u.input.update(keys, runes, ts)
		}
	}
}

func (u *userInterfaceImpl) SetForeground(foreground bool) error {
	var v int32
	if foreground {
		v = 1
	}
	atomic.StoreInt32(&u.foreground, v)

	if foreground {
		return hooks.ResumeAudio()
	} else {
		return hooks.SuspendAudio()
	}
}

func (u *userInterfaceImpl) Run(game Game) error {
	u.setGBuildSizeCh = make(chan struct{})
	go func() {
		if err := u.run(game, true); err != nil {
			// As mobile apps never ends, Loop can't return. Just panic here.
			panic(err)
		}
	}()
	app.Main(u.appMain)
	return nil
}

func RunWithoutMainLoop(game Game) {
	theUI.runWithoutMainLoop(game)
}

func (u *userInterfaceImpl) runWithoutMainLoop(game Game) {
	go func() {
		if err := u.run(game, false); err != nil {
			u.errCh <- err
		}
	}()
}

func (u *userInterfaceImpl) run(game Game, mainloop bool) (err error) {
	// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
	// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
	// unfortunately.
	// TODO: Panic on other goroutines cannot be handled here.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v\n%s", r, string(debug.Stack()))
		}
	}()

	u.context = newContextImpl(game)

	if mainloop {
		// When mainloop is true, gomobile-build is used. In this case, GL functions must be called via
		// gl.Context so that they are called on the appropriate thread.
		g := opengl.Get()
		u.graphicsDriver = g

		ctx := <-glContextCh
		g.SetGomobileGLContext(ctx)
	} else {
		u.graphicsDriver = graphicsDriver()
		u.t = thread.NewOSThread()
		graphicscommand.SetRenderingThread(u.t)
	}

	// If gomobile-build is used, wait for the outside size fixed.
	if u.setGBuildSizeCh != nil {
		<-u.setGBuildSizeCh
	}

	for {
		if err := u.update(); err != nil {
			return err
		}
	}
}

// outsideSize must be called on the same goroutine as update().
func (u *userInterfaceImpl) outsideSize() (float64, float64) {
	var outsideWidth, outsideHeight float64

	u.m.RLock()
	if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
		outsideWidth = u.outsideWidth
		outsideHeight = u.outsideHeight
	} else {
		// gomobile build
		d := deviceScale()
		outsideWidth = float64(u.gbuildWidthPx) / d
		outsideHeight = float64(u.gbuildHeightPx) / d
	}
	u.m.RUnlock()

	return outsideWidth, outsideHeight
}

func (u *userInterfaceImpl) update() error {
	<-renderCh
	defer func() {
		renderEndCh <- struct{}{}
	}()

	w, h := u.outsideSize()
	if err := u.context.updateFrame(u.graphicsDriver, w, h, deviceScale()); err != nil {
		return err
	}
	return nil
}

func (u *userInterfaceImpl) ScreenSizeInFullscreen() (int, int) {
	// TODO: This function should return gbuildWidthPx, gbuildHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

// SetOutsideSize is called from mobile/ebitenmobileview.
//
// SetOutsideSize is concurrent safe.
func (u *userInterfaceImpl) SetOutsideSize(outsideWidth, outsideHeight float64) {
	u.m.Lock()
	if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
		u.outsideWidth = outsideWidth
		u.outsideHeight = outsideHeight
	}
	u.m.Unlock()
}

func (u *userInterfaceImpl) setGBuildSize(widthPx, heightPx int) {
	u.m.Lock()
	u.gbuildWidthPx = widthPx
	u.gbuildHeightPx = heightPx
	u.m.Unlock()

	u.once.Do(func() {
		close(u.setGBuildSizeCh)
	})
}

func (u *userInterfaceImpl) adjustPosition(x, y int) (int, int) {
	xf, yf := u.context.adjustPosition(float64(x), float64(y), deviceScale())
	return int(xf), int(yf)
}

func (u *userInterfaceImpl) CursorMode() CursorMode {
	return CursorModeHidden
}

func (u *userInterfaceImpl) SetCursorMode(mode CursorMode) {
	// Do nothing
}

func (u *userInterfaceImpl) CursorShape() CursorShape {
	return CursorShapeDefault
}

func (u *userInterfaceImpl) SetCursorShape(shape CursorShape) {
	// Do nothing
}

func (u *userInterfaceImpl) IsFullscreen() bool {
	return false
}

func (u *userInterfaceImpl) SetFullscreen(fullscreen bool) {
	// Do nothing
}

func (u *userInterfaceImpl) IsFocused() bool {
	return atomic.LoadInt32(&u.foreground) != 0
}

func (u *userInterfaceImpl) IsRunnableOnUnfocused() bool {
	return false
}

func (u *userInterfaceImpl) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
	// Do nothing
}

func (u *userInterfaceImpl) SetFPSMode(mode FPSModeType) {
	u.fpsMode = mode
	u.updateExplicitRenderingModeIfNeeded()
}

func (u *userInterfaceImpl) updateExplicitRenderingModeIfNeeded() {
	if u.renderRequester == nil {
		return
	}
	u.renderRequester.SetExplicitRenderingMode(u.fpsMode == FPSModeVsyncOffMinimum)
}

func (u *userInterfaceImpl) DeviceScaleFactor() float64 {
	return deviceScale()
}

func (u *userInterfaceImpl) SetScreenTransparent(transparent bool) {
	// Do nothing
}

func (u *userInterfaceImpl) IsScreenTransparent() bool {
	return false
}

func (u *userInterfaceImpl) resetForTick() {
	u.input.resetForTick()
}

func (u *userInterfaceImpl) SetInitFocused(focused bool) {
	// Do nothing
}

func (u *userInterfaceImpl) Input() *Input {
	return &u.input
}

func (u *userInterfaceImpl) Window() *Window {
	return &Window{}
}

type Touch struct {
	ID TouchID
	X  int
	Y  int
}

func (u *userInterfaceImpl) UpdateInput(keys map[Key]struct{}, runes []rune, touches []Touch) {
	u.input.update(keys, runes, touches)
	if u.fpsMode == FPSModeVsyncOffMinimum {
		u.renderRequester.RequestRenderIfNeeded()
	}
}

type RenderRequester interface {
	SetExplicitRenderingMode(explicitRendering bool)
	RequestRenderIfNeeded()
}

func (u *userInterfaceImpl) SetRenderRequester(renderRequester RenderRequester) {
	u.renderRequester = renderRequester
	u.updateExplicitRenderingModeIfNeeded()
}

func (u *userInterfaceImpl) ScheduleFrame() {
	if u.renderRequester != nil && u.fpsMode == FPSModeVsyncOffMinimum {
		u.renderRequester.RequestRenderIfNeeded()
	}
}
<|endoftext|>


<commit_before>package dispatcher

import (
	"net"
	"testing"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/grpc"

	"github.com/docker/swarm-v2/api"
	"github.com/docker/swarm-v2/state"
	"github.com/stretchr/testify/assert"
)

type grpcDispatcher struct {
	Client      api.AgentClient
	Store       state.Store
	grpcServer  *grpc.Server
	agentServer api.AgentServer
	conn        *grpc.ClientConn
}

func (gd *grpcDispatcher) Close() {
	gd.grpcServer.Stop()
}

func startDispatcher(c *Config) (*grpcDispatcher, error) {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return nil, err
	}
	s := grpc.NewServer()
	store := state.NewMemoryStore()
	d := New(store, c)
	api.RegisterAgentServer(s, d)
	go func() {
		// Serve will always return an error (even when properly stopped).
		// Explictly ignore it.
		_ = s.Serve(l)
	}()
	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure(), grpc.WithTimeout(10*time.Second))
	if err != nil {
		s.Stop()
		return nil, err
	}
	cli := api.NewAgentClient(conn)
	return &grpcDispatcher{
		Client:      cli,
		Store:       store,
		agentServer: d,
		conn:        conn,
		grpcServer:  s,
	}, nil
}

func TestRegisterTwice(t *testing.T) {
	gd, err := startDispatcher(DefaultConfig())
	assert.NoError(t, err)
	defer gd.Close()

	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.Nil(t, resp)
		assert.Error(t, err)
		assert.Equal(t, grpc.ErrorDesc(err), ErrNodeAlreadyRegistered.Error())
	}
}

func TestHeartbeat(t *testing.T) {
	cfg := DefaultConfig()
	cfg.HeartbeatPeriod = 500 * time.Millisecond
	cfg.HeartbeatEpsilon = 0
	gd, err := startDispatcher(cfg)
	assert.NoError(t, err)
	defer gd.Close()

	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	time.Sleep(250 * time.Millisecond)

	resp, err := gd.Client.Heartbeat(context.Background(), &api.HeartbeatRequest{NodeID: testNode.ID})
	assert.NoError(t, err)
	assert.NotZero(t, resp.TTL)
	time.Sleep(300 * time.Millisecond)

	err = gd.Store.View(func(readTx state.ReadTx) error {
		storeNode := readTx.Nodes().Get("test")
		assert.NotNil(t, storeNode)
		assert.Equal(t, storeNode.Status.State, api.NodeStatus_READY)
		return nil
	})
	assert.NoError(t, err)
}

func TestHeartbeatTimeout(t *testing.T) {
	cfg := DefaultConfig()
	cfg.HeartbeatPeriod = 100 * time.Millisecond
	cfg.HeartbeatEpsilon = 0
	gd, err := startDispatcher(cfg)
	assert.NoError(t, err)
	defer gd.Close()

	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	time.Sleep(500 * time.Millisecond)

	err = gd.Store.View(func(readTx state.ReadTx) error {
		storeNode := readTx.Nodes().Get("test")
		assert.NotNil(t, storeNode)
		assert.Equal(t, api.NodeStatus_DOWN, storeNode.Status.State)
		return nil
	})
	assert.NoError(t, err)

	// check that node is deregistered
	resp, err := gd.Client.Heartbeat(context.Background(), &api.HeartbeatRequest{NodeID: testNode.ID})
	assert.Nil(t, resp)
	assert.Error(t, err)
	assert.Equal(t, grpc.ErrorDesc(err), ErrNodeNotRegistered.Error())
}

func TestHeartbeatUnregistered(t *testing.T) {
	gd, err := startDispatcher(DefaultConfig())
	assert.NoError(t, err)
	defer gd.Close()
	resp, err := gd.Client.Heartbeat(context.Background(), &api.HeartbeatRequest{NodeID: "test"})
	assert.Nil(t, resp)
	assert.Error(t, err)
	assert.Equal(t, grpc.ErrorDesc(err), ErrNodeNotRegistered.Error())
}

func TestTasks(t *testing.T) {
	gd, err := startDispatcher(DefaultConfig())
	assert.NoError(t, err)
	defer gd.Close()
	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	testTask := &api.Task{
		ID:     "testTask",
		NodeID: testNode.ID,
	}
	stream, err := gd.Client.Tasks(context.Background(), &api.TasksRequest{NodeID: testNode.ID})
	assert.NoError(t, err)

	err = gd.Store.Update(func(tx state.Tx) error {
		assert.NoError(t, tx.Tasks().Create(testTask))
		return nil
	})
	assert.NoError(t, err)

	resp, err := stream.Recv()
	assert.NoError(t, err)
	assert.Equal(t, len(resp.Tasks), 1)
}

func TestTaskUpdate(t *testing.T) {
	gd, err := startDispatcher(DefaultConfig())
	assert.NoError(t, err)
	defer gd.Close()

	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	testTask1 := &api.Task{
		ID:     "testTask1",
		NodeID: testNode.ID,
	}
	testTask2 := &api.Task{
		ID:     "testTask2",
		NodeID: testNode.ID,
	}
	err = gd.Store.Update(func(tx state.Tx) error {
		assert.NoError(t, tx.Tasks().Create(testTask1))
		assert.NoError(t, tx.Tasks().Create(testTask2))
		return nil
	})
	assert.NoError(t, err)

	testTask1.Status = &api.TaskStatus{State: api.TaskStatus_ASSIGNED}
	testTask2.Status = &api.TaskStatus{State: api.TaskStatus_ASSIGNED}
	updReq := &api.UpdateTaskStatusRequest{
		NodeID: testNode.ID,
		Tasks:  []*api.Task{testTask1, testTask2},
	}
	_, err = gd.Client.UpdateTaskStatus(context.Background(), updReq)
	assert.NoError(t, err)
	err = gd.Store.View(func(readTx state.ReadTx) error {
		storeTask1 := readTx.Tasks().Get(testTask1.ID)
		assert.NotNil(t, storeTask1)
		storeTask2 := readTx.Tasks().Get(testTask2.ID)
		assert.NotNil(t, storeTask2)
		assert.Equal(t, storeTask1.Status.State, api.TaskStatus_ASSIGNED)
		assert.Equal(t, storeTask2.Status.State, api.TaskStatus_ASSIGNED)
		return nil
	})
	assert.NoError(t, err)
}

func TestPeriodChooser(t *testing.T) {
	period := 100 * time.Millisecond
	epsilon := 50 * time.Millisecond
	pc := newPeriodChooser(period, epsilon)
	for i := 0; i < 1024; i++ {
		ttl := pc.Choose()
		if ttl < period-epsilon {
			t.Fatalf("ttl elected below epsilon range: %v", ttl)
		} else if ttl > period+epsilon {
			t.Fatalf("ttl elected above epsilon range: %v", ttl)
		}
	}
}
<commit_msg>dispatcher tests: Close the client connection.<commit_after>package dispatcher

import (
	"net"
	"testing"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/grpc"

	"github.com/docker/swarm-v2/api"
	"github.com/docker/swarm-v2/state"
	"github.com/stretchr/testify/assert"
)

type grpcDispatcher struct {
	Client      api.AgentClient
	Store       state.Store
	grpcServer  *grpc.Server
	agentServer api.AgentServer
	conn        *grpc.ClientConn
}

func (gd *grpcDispatcher) Close() {
	// Close the client connection.
	_ = gd.conn.Close()
	gd.grpcServer.Stop()
}

func startDispatcher(c *Config) (*grpcDispatcher, error) {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return nil, err
	}
	s := grpc.NewServer()
	store := state.NewMemoryStore()
	d := New(store, c)
	api.RegisterAgentServer(s, d)
	go func() {
		// Serve will always return an error (even when properly stopped).
		// Explictly ignore it.
		_ = s.Serve(l)
	}()
	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure(), grpc.WithTimeout(10*time.Second))
	if err != nil {
		s.Stop()
		return nil, err
	}
	cli := api.NewAgentClient(conn)
	return &grpcDispatcher{
		Client:      cli,
		Store:       store,
		agentServer: d,
		conn:        conn,
		grpcServer:  s,
	}, nil
}

func TestRegisterTwice(t *testing.T) {
	gd, err := startDispatcher(DefaultConfig())
	assert.NoError(t, err)
	defer gd.Close()

	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.Nil(t, resp)
		assert.Error(t, err)
		assert.Equal(t, grpc.ErrorDesc(err), ErrNodeAlreadyRegistered.Error())
	}
}

func TestHeartbeat(t *testing.T) {
	cfg := DefaultConfig()
	cfg.HeartbeatPeriod = 500 * time.Millisecond
	cfg.HeartbeatEpsilon = 0
	gd, err := startDispatcher(cfg)
	assert.NoError(t, err)
	defer gd.Close()

	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	time.Sleep(250 * time.Millisecond)

	resp, err := gd.Client.Heartbeat(context.Background(), &api.HeartbeatRequest{NodeID: testNode.ID})
	assert.NoError(t, err)
	assert.NotZero(t, resp.TTL)
	time.Sleep(300 * time.Millisecond)

	err = gd.Store.View(func(readTx state.ReadTx) error {
		storeNode := readTx.Nodes().Get("test")
		assert.NotNil(t, storeNode)
		assert.Equal(t, storeNode.Status.State, api.NodeStatus_READY)
		return nil
	})
	assert.NoError(t, err)
}

func TestHeartbeatTimeout(t *testing.T) {
	cfg := DefaultConfig()
	cfg.HeartbeatPeriod = 100 * time.Millisecond
	cfg.HeartbeatEpsilon = 0
	gd, err := startDispatcher(cfg)
	assert.NoError(t, err)
	defer gd.Close()

	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	time.Sleep(500 * time.Millisecond)

	err = gd.Store.View(func(readTx state.ReadTx) error {
		storeNode := readTx.Nodes().Get("test")
		assert.NotNil(t, storeNode)
		assert.Equal(t, api.NodeStatus_DOWN, storeNode.Status.State)
		return nil
	})
	assert.NoError(t, err)

	// check that node is deregistered
	resp, err := gd.Client.Heartbeat(context.Background(), &api.HeartbeatRequest{NodeID: testNode.ID})
	assert.Nil(t, resp)
	assert.Error(t, err)
	assert.Equal(t, grpc.ErrorDesc(err), ErrNodeNotRegistered.Error())
}

func TestHeartbeatUnregistered(t *testing.T) {
	gd, err := startDispatcher(DefaultConfig())
	assert.NoError(t, err)
	defer gd.Close()
	resp, err := gd.Client.Heartbeat(context.Background(), &api.HeartbeatRequest{NodeID: "test"})
	assert.Nil(t, resp)
	assert.Error(t, err)
	assert.Equal(t, grpc.ErrorDesc(err), ErrNodeNotRegistered.Error())
}

func TestTasks(t *testing.T) {
	gd, err := startDispatcher(DefaultConfig())
	assert.NoError(t, err)
	defer gd.Close()
	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	testTask := &api.Task{
		ID:     "testTask",
		NodeID: testNode.ID,
	}
	stream, err := gd.Client.Tasks(context.Background(), &api.TasksRequest{NodeID: testNode.ID})
	assert.NoError(t, err)

	err = gd.Store.Update(func(tx state.Tx) error {
		assert.NoError(t, tx.Tasks().Create(testTask))
		return nil
	})
	assert.NoError(t, err)

	resp, err := stream.Recv()
	assert.NoError(t, err)
	assert.Equal(t, len(resp.Tasks), 1)
}

func TestTaskUpdate(t *testing.T) {
	gd, err := startDispatcher(DefaultConfig())
	assert.NoError(t, err)
	defer gd.Close()

	testNode := &api.NodeSpec{ID: "test"}
	{
		resp, err := gd.Client.Register(context.Background(), &api.RegisterRequest{Spec: testNode})
		assert.NoError(t, err)
		assert.NotZero(t, resp.TTL)
	}
	testTask1 := &api.Task{
		ID:     "testTask1",
		NodeID: testNode.ID,
	}
	testTask2 := &api.Task{
		ID:     "testTask2",
		NodeID: testNode.ID,
	}
	err = gd.Store.Update(func(tx state.Tx) error {
		assert.NoError(t, tx.Tasks().Create(testTask1))
		assert.NoError(t, tx.Tasks().Create(testTask2))
		return nil
	})
	assert.NoError(t, err)

	testTask1.Status = &api.TaskStatus{State: api.TaskStatus_ASSIGNED}
	testTask2.Status = &api.TaskStatus{State: api.TaskStatus_ASSIGNED}
	updReq := &api.UpdateTaskStatusRequest{
		NodeID: testNode.ID,
		Tasks:  []*api.Task{testTask1, testTask2},
	}
	_, err = gd.Client.UpdateTaskStatus(context.Background(), updReq)
	assert.NoError(t, err)
	err = gd.Store.View(func(readTx state.ReadTx) error {
		storeTask1 := readTx.Tasks().Get(testTask1.ID)
		assert.NotNil(t, storeTask1)
		storeTask2 := readTx.Tasks().Get(testTask2.ID)
		assert.NotNil(t, storeTask2)
		assert.Equal(t, storeTask1.Status.State, api.TaskStatus_ASSIGNED)
		assert.Equal(t, storeTask2.Status.State, api.TaskStatus_ASSIGNED)
		return nil
	})
	assert.NoError(t, err)
}

func TestPeriodChooser(t *testing.T) {
	period := 100 * time.Millisecond
	epsilon := 50 * time.Millisecond
	pc := newPeriodChooser(period, epsilon)
	for i := 0; i < 1024; i++ {
		ttl := pc.Choose()
		if ttl < period-epsilon {
			t.Fatalf("ttl elected below epsilon range: %v", ttl)
		} else if ttl > period+epsilon {
			t.Fatalf("ttl elected above epsilon range: %v", ttl)
		}
	}
}
<|endoftext|>


<commit_before>package operator

import (
	"archive/tar"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strings"
)

const (
	// MaxPartitionSize represents the maximun size of a partition.
	MaxPartitionSize = 1610612736
	// MaxPartitionMembers represents the maximun numbers of menbers in a partition.
	MaxPartitionMembers = int(MaxPartitionSize / 262144)
)

// File represents an archive file.
type File struct {
	Path     string
	Rel      string
	FileInfo os.FileInfo
}

// String returns the path of a file.
func (f *File) String() string {
	return f.Path
}

// Tape represents an archive.
type Tape []*File

// Partition creates multiple tapes for the given directory.
func Partition(cluster string) (tapes []Tape, err error) {
	var size int64
	var tape Tape
	files, err := walk(cluster)
	if err != nil {
		return nil, err
	}
	for _, file := range files {
		if file.FileInfo.Size() > MaxPartitionSize {
			// File is bigger than the max size of partition
			return nil, errors.New("file too big for tar partition")
		}
		if (size+file.FileInfo.Size() >= MaxPartitionSize) ||
			(len(tape) >= MaxPartitionMembers) {
			tapes = append(tapes, tape)
			tape = make(Tape, 0)
			size = 0
		}
		tape = append(tape, file)
		size += file.FileInfo.Size()
	}
	return append(tapes, tape), nil
}

// Copy writes a tar archive of all members.
func (t Tape) Copy(w io.WriteCloser) error {
	archive := tar.NewWriter(w)
	defer archive.Close()
	for _, member := range t {
		file, err := os.Open(member.Path)
		if err != nil {
			// File might have been deleted, we can ignore it.
			if os.IsNotExist(err) {
				continue
			}
			return err
		}
		link, err := filepath.EvalSymlinks(member.Path)
		if err != nil {
			return err
		}
		header, err := tar.FileInfoHeader(member.FileInfo, link)
		if err != nil {
			return err
		}
		header.Name = member.Rel
		if err := archive.WriteHeader(header); err != nil {
			return err
		}
		if !member.FileInfo.Mode().IsRegular() {
			continue
		}
		if _, err = io.Copy(archive, file); err != nil {
			if err != tar.ErrWriteTooLong {
				return err
			}
		}
		if err := file.Close(); err != nil {
			return err
		}
	}
	return nil
}

func walk(cluster string) (files []*File, err error) {
	err = filepath.Walk(cluster, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			// An error occured, stop processing
			return err
		}
		if ignoreFile(info.Name()) {
			// Ignore configuration, pid and other files
			return nil
		}
		rel, err := filepath.Rel(cluster, path)
		if err != nil {
			return err
		}
		files = append(files, &File{
			Path:     path,
			Rel:      rel,
			FileInfo: info,
		})
		if keepEmpty(path) && info.IsDir() {
			// We don't want to archive WAL files, nor temporary files, nor log
			// files but we want to keep the directory that contains them.
			return filepath.SkipDir
		}
		return nil
	})
	return files, err
}

// Unite untar a partition for the given directory.
func Unite(cluster string, partition io.ReadCloser) error {
	archive := tar.NewReader(partition)
	for {
		header, err := archive.Next()
		if err != nil {
			if err == io.EOF {
				// End of archive
				break
			}
			return err
		}
		filename := filepath.Join(cluster, header.Name)
		info := header.FileInfo()
		if info.IsDir() {
			os.MkdirAll(filename, info.Mode())
			continue
		}
		file, err := createFile(filename, info.Mode())
		if err != nil {
			return err
		}
		if _, err = io.Copy(file, archive); err != nil {
			return err
		}
	}
	return nil
}

func createFile(name string, mode os.FileMode) (*os.File, error) {
	if err := os.MkdirAll(filepath.Dir(name), 0700); err != nil {
		return nil, err
	}
	return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
}

func isSymlink(fi os.FileInfo) bool {
	return fi.Mode()&os.ModeSymlink == os.ModeSymlink
}

func ignoreFile(filename string) bool {
	files := []string{
		"postmaster.pid", "postmaster.opts", "postgresql.conf", "pg_hba.conf",
		"recovery.conf", "recovery.done", "pg_ident.conf", "promote",
	}
	for _, file := range files {
		if file == filename {
			return true
		}
	}
	return false
}

func keepEmpty(path string) bool {
	directories := []string{"pg_xlog", "pg_log", "pg_replslot", "pg_wal", "pgsql_tmp", "pg_stat_tmp"}
	for _, name := range directories {
		if strings.Contains(path, name) {
			return true
		}
	}
	return false
}
<commit_msg>Small optimization<commit_after>package operator

import (
	"archive/tar"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strings"
)

const (
	// MaxPartitionSize represents the maximun size of a partition.
	MaxPartitionSize = 1610612736
	// MaxPartitionMembers represents the maximun numbers of menbers in a partition.
	MaxPartitionMembers = int(MaxPartitionSize / 262144)
)

// File represents an archive file.
type File struct {
	Path     string
	Rel      string
	FileInfo os.FileInfo
}

// String returns the path of a file.
func (f *File) String() string {
	return f.Path
}

// Tape represents an archive.
type Tape []*File

// Partition creates multiple tapes for the given directory.
func Partition(cluster string) (tapes []Tape, err error) {
	var size int64
	var tape Tape
	files, err := walk(cluster)
	if err != nil {
		return nil, err
	}
	for _, file := range files {
		if file.FileInfo.Size() > MaxPartitionSize {
			// File is bigger than the max size of partition
			return nil, errors.New("file too big for tar partition")
		}
		if (size+file.FileInfo.Size() >= MaxPartitionSize) ||
			(len(tape) >= MaxPartitionMembers) {
			tapes = append(tapes, tape)
			tape = make(Tape, 0)
			size = 0
		}
		tape = append(tape, file)
		size += file.FileInfo.Size()
	}
	return append(tapes, tape), nil
}

// Copy writes a tar archive of all members.
func (t Tape) Copy(w io.WriteCloser) error {
	archive := tar.NewWriter(w)
	defer archive.Close()
	for _, member := range t {
		file, err := os.Open(member.Path)
		if err != nil {
			// File might have been deleted, we can ignore it.
			if os.IsNotExist(err) {
				continue
			}
			return err
		}
		var link string
		if isSymlink(member.FileInfo) {
			if link, err = filepath.EvalSymlinks(member.Path); err != nil {
				return err
			}
		}
		header, err := tar.FileInfoHeader(member.FileInfo, link)
		if err != nil {
			return err
		}
		header.Name = member.Rel
		if err := archive.WriteHeader(header); err != nil {
			return err
		}
		if !member.FileInfo.Mode().IsRegular() {
			continue
		}
		if _, err = io.Copy(archive, file); err != nil {
			if err != tar.ErrWriteTooLong {
				return err
			}
		}
		if err := file.Close(); err != nil {
			return err
		}
	}
	return nil
}

func walk(cluster string) (files []*File, err error) {
	err = filepath.Walk(cluster, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			// An error occured, stop processing
			return err
		}
		if ignoreFile(info.Name()) {
			// Ignore configuration, pid and other files
			return nil
		}
		rel, err := filepath.Rel(cluster, path)
		if err != nil {
			return err
		}
		files = append(files, &File{
			Path:     path,
			Rel:      rel,
			FileInfo: info,
		})
		if keepEmpty(path) && info.IsDir() {
			// We don't want to archive WAL files, nor temporary files, nor log
			// files but we want to keep the directory that contains them.
			return filepath.SkipDir
		}
		return nil
	})
	return files, err
}

// Unite untar a partition for the given directory.
func Unite(cluster string, partition io.ReadCloser) error {
	archive := tar.NewReader(partition)
	for {
		header, err := archive.Next()
		if err != nil {
			if err == io.EOF {
				// End of archive
				break
			}
			return err
		}
		filename := filepath.Join(cluster, header.Name)
		info := header.FileInfo()
		if info.IsDir() {
			os.MkdirAll(filename, info.Mode())
			continue
		}
		file, err := createFile(filename, info.Mode())
		if err != nil {
			return err
		}
		if _, err = io.Copy(file, archive); err != nil {
			return err
		}
	}
	return nil
}

func createFile(name string, mode os.FileMode) (*os.File, error) {
	if err := os.MkdirAll(filepath.Dir(name), 0700); err != nil {
		return nil, err
	}
	return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode)
}

func isSymlink(fi os.FileInfo) bool {
	return fi.Mode()&os.ModeSymlink != 0
}

func ignoreFile(filename string) bool {
	files := []string{
		"postmaster.pid", "postmaster.opts", "postgresql.conf", "pg_hba.conf",
		"recovery.conf", "recovery.done", "pg_ident.conf", "promote",
	}
	for _, file := range files {
		if file == filename {
			return true
		}
	}
	return false
}

func keepEmpty(path string) bool {
	directories := []string{"pg_xlog", "pg_log", "pg_replslot", "pg_wal", "pgsql_tmp", "pg_stat_tmp"}
	for _, name := range directories {
		if strings.Contains(path, name) {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package pagination

import (
	"errors"
	"fmt"
	"net/http"
	"reflect"

	"github.com/rackspace/gophercloud"
)

var (
	// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
	ErrPageNotAvailable = errors.New("The requested page does not exist.")
)

// Page must be satisfied by the result type of any resource collection.
// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
// instead.
// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
// will need to implement.
type Page interface {

	// NextPageURL generates the URL for the page of data that follows this collection.
	// Return "" if no such page exists.
	NextPageURL() (string, error)

	// IsEmpty returns true if this Page has no items in it.
	IsEmpty() (bool, error)

	// GetBody returns the Page Body. This is used in the `AllPages` method.
	GetBody() interface{}
}

// Pager knows how to advance through a specific resource collection, one page at a time.
type Pager struct {
	client *gophercloud.ServiceClient

	initialURL string

	createPage func(r PageResult) Page

	Err error

	// Headers supplies additional HTTP headers to populate on each paged request.
	Headers map[string]string

	// PageType is the type of `Page` the `Extract*` function expects back. This is
	// needed because a type assertion occurs in each `Extract*` function, and it will
	// fail if the `Page` doesn't have the expected type.
	PageType Page
}

// NewPager constructs a manually-configured pager.
// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
	return Pager{
		client:     client,
		initialURL: initialURL,
		createPage: createPage,
	}
}

// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
// useful for overriding List functions in delegation.
func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
	return Pager{
		client:     p.client,
		initialURL: p.initialURL,
		createPage: createPage,
	}
}

func (p Pager) fetchNextPage(url string) (Page, error) {
	resp, err := Request(p.client, p.Headers, url)
	if err != nil {
		return nil, err
	}

	remembered, err := PageResultFrom(resp)
	if err != nil {
		return nil, err
	}

	return p.createPage(remembered), nil
}

// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
// Return "false" from the handler to prematurely stop iterating.
func (p Pager) EachPage(handler func(Page) (bool, error)) error {
	if p.Err != nil {
		return p.Err
	}
	currentURL := p.initialURL
	for {
		currentPage, err := p.fetchNextPage(currentURL)
		if err != nil {
			return err
		}

		empty, err := currentPage.IsEmpty()
		if err != nil {
			return err
		}
		if empty {
			return nil
		}

		ok, err := handler(currentPage)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}

		currentURL, err = currentPage.NextPageURL()
		if err != nil {
			return err
		}
		if currentURL == "" {
			return nil
		}
	}
}

// AllPages returns all the pages from a `List` operation in a single page,
// allowing the user to retrieve all the pages at once.
func (p Pager) AllPages() (Page, error) {
	// Having a value of `nil` for `p.PageType` will cause a run-time error.
	if p.PageType == nil {
		return nil, fmt.Errorf("Pager field PageType must be set to successfully call pagination.AllPages method.")
	}
	// pagesSlice holds all the pages until they get converted into as Page Body.
	var pagesSlice []interface{}
	// body will contain the final concatenated Page body.
	var body reflect.Value

	// Grab a test page to ascertain the page body type.
	testPage, err := p.fetchNextPage(p.initialURL)
	if err != nil {
		return nil, err
	}

	// Switch on the page body type. Recognized types are `map[string]interface{}`,
	// `[]byte`, and `[]interface{}`.
	switch testPage.GetBody().(type) {
	case map[string]interface{}:
		// key is the map key for the page body if the body type is `map[string]interface{}`.
		var key string
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().(map[string]interface{})
			for k := range b {
				// If it's a linked page, we don't want the `links`, we want the other one.
				if k != "links" {
					key = k
				}
			}
			pagesSlice = append(pagesSlice, b[key].([]interface{})...)
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Set body to value of type `map[string]interface{}`
		body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
		body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
	case []byte:
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().([]byte)
			pagesSlice = append(pagesSlice, b)
			// seperate pages with a comma
			pagesSlice = append(pagesSlice, []byte{10})
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Remove the trailing comma.
		pagesSlice = pagesSlice[:len(pagesSlice)-1]
		var b []byte
		// Combine the slice of slices in to a single slice.
		for _, slice := range pagesSlice {
			b = append(b, slice.([]byte)...)
		}
		// Set body to value of type `bytes`.
		body = reflect.New(reflect.TypeOf(b)).Elem()
		body.SetBytes(b)
	case []interface{}:
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().([]interface{})
			pagesSlice = append(pagesSlice, b...)
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Set body to value of type `[]interface{}`
		body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
		for i, s := range pagesSlice {
			body.Index(i).Set(reflect.ValueOf(s))
		}
	default:
		return nil, fmt.Errorf("Page body has unrecognized type.")
	}

	// Each `Extract*` function is expecting a specific type of page coming back,
	// otherwise the type assertion in those functions will fail. PageType is needed
	// to create a type in this method that has the same type that the `Extract*`
	// function is expecting and set the Body of that object to the concatenated
	// pages.
	page := reflect.New(reflect.TypeOf(p.PageType))
	// Set the page body to be the concatenated pages.
	page.Elem().FieldByName("Body").Set(body)
	// Set any additional headers that were pass along. The `objectstorage` pacakge,
	// for example, passes a Content-Type header.
	h := make(http.Header)
	for k, v := range p.Headers {
		h.Add(k, v)
	}
	page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
	// Type assert the page to a Page interface so that the type assertion in the
	// `Extract*` methods will work.
	return page.Elem().Interface().(Page), err
}
<commit_msg>use strings.HasSuffix to check for links key<commit_after>package pagination

import (
	"errors"
	"fmt"
	"net/http"
	"reflect"
	"strings"

	"github.com/rackspace/gophercloud"
)

var (
	// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
	ErrPageNotAvailable = errors.New("The requested page does not exist.")
)

// Page must be satisfied by the result type of any resource collection.
// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
// instead.
// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
// will need to implement.
type Page interface {

	// NextPageURL generates the URL for the page of data that follows this collection.
	// Return "" if no such page exists.
	NextPageURL() (string, error)

	// IsEmpty returns true if this Page has no items in it.
	IsEmpty() (bool, error)

	// GetBody returns the Page Body. This is used in the `AllPages` method.
	GetBody() interface{}
}

// Pager knows how to advance through a specific resource collection, one page at a time.
type Pager struct {
	client *gophercloud.ServiceClient

	initialURL string

	createPage func(r PageResult) Page

	Err error

	// Headers supplies additional HTTP headers to populate on each paged request.
	Headers map[string]string

	// PageType is the type of `Page` the `Extract*` function expects back. This is
	// needed because a type assertion occurs in each `Extract*` function, and it will
	// fail if the `Page` doesn't have the expected type.
	PageType Page
}

// NewPager constructs a manually-configured pager.
// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
	return Pager{
		client:     client,
		initialURL: initialURL,
		createPage: createPage,
	}
}

// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
// useful for overriding List functions in delegation.
func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
	return Pager{
		client:     p.client,
		initialURL: p.initialURL,
		createPage: createPage,
	}
}

func (p Pager) fetchNextPage(url string) (Page, error) {
	resp, err := Request(p.client, p.Headers, url)
	if err != nil {
		return nil, err
	}

	remembered, err := PageResultFrom(resp)
	if err != nil {
		return nil, err
	}

	return p.createPage(remembered), nil
}

// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
// Return "false" from the handler to prematurely stop iterating.
func (p Pager) EachPage(handler func(Page) (bool, error)) error {
	if p.Err != nil {
		return p.Err
	}
	currentURL := p.initialURL
	for {
		currentPage, err := p.fetchNextPage(currentURL)
		if err != nil {
			return err
		}

		empty, err := currentPage.IsEmpty()
		if err != nil {
			return err
		}
		if empty {
			return nil
		}

		ok, err := handler(currentPage)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}

		currentURL, err = currentPage.NextPageURL()
		if err != nil {
			return err
		}
		if currentURL == "" {
			return nil
		}
	}
}

// AllPages returns all the pages from a `List` operation in a single page,
// allowing the user to retrieve all the pages at once.
func (p Pager) AllPages() (Page, error) {
	// Having a value of `nil` for `p.PageType` will cause a run-time error.
	if p.PageType == nil {
		return nil, fmt.Errorf("Pager field PageType must be set to successfully call pagination.AllPages method.")
	}
	// pagesSlice holds all the pages until they get converted into as Page Body.
	var pagesSlice []interface{}
	// body will contain the final concatenated Page body.
	var body reflect.Value

	// Grab a test page to ascertain the page body type.
	testPage, err := p.fetchNextPage(p.initialURL)
	if err != nil {
		return nil, err
	}

	// Switch on the page body type. Recognized types are `map[string]interface{}`,
	// `[]byte`, and `[]interface{}`.
	switch testPage.GetBody().(type) {
	case map[string]interface{}:
		// key is the map key for the page body if the body type is `map[string]interface{}`.
		var key string
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().(map[string]interface{})
			for k := range b {
				// If it's a linked page, we don't want the `links`, we want the other one.
				if !strings.HasSuffix(k, "links") {
					key = k
				}
			}
			pagesSlice = append(pagesSlice, b[key].([]interface{})...)
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Set body to value of type `map[string]interface{}`
		body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
		body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
	case []byte:
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().([]byte)
			pagesSlice = append(pagesSlice, b)
			// seperate pages with a comma
			pagesSlice = append(pagesSlice, []byte{10})
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Remove the trailing comma.
		pagesSlice = pagesSlice[:len(pagesSlice)-1]
		var b []byte
		// Combine the slice of slices in to a single slice.
		for _, slice := range pagesSlice {
			b = append(b, slice.([]byte)...)
		}
		// Set body to value of type `bytes`.
		body = reflect.New(reflect.TypeOf(b)).Elem()
		body.SetBytes(b)
	case []interface{}:
		// Iterate over the pages to concatenate the bodies.
		err := p.EachPage(func(page Page) (bool, error) {
			b := page.GetBody().([]interface{})
			pagesSlice = append(pagesSlice, b...)
			return true, nil
		})
		if err != nil {
			return nil, err
		}
		// Set body to value of type `[]interface{}`
		body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
		for i, s := range pagesSlice {
			body.Index(i).Set(reflect.ValueOf(s))
		}
	default:
		return nil, fmt.Errorf("Page body has unrecognized type.")
	}

	// Each `Extract*` function is expecting a specific type of page coming back,
	// otherwise the type assertion in those functions will fail. PageType is needed
	// to create a type in this method that has the same type that the `Extract*`
	// function is expecting and set the Body of that object to the concatenated
	// pages.
	page := reflect.New(reflect.TypeOf(p.PageType))
	// Set the page body to be the concatenated pages.
	page.Elem().FieldByName("Body").Set(body)
	// Set any additional headers that were pass along. The `objectstorage` pacakge,
	// for example, passes a Content-Type header.
	h := make(http.Header)
	for k, v := range p.Headers {
		h.Add(k, v)
	}
	page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
	// Type assert the page to a Page interface so that the type assertion in the
	// `Extract*` methods will work.
	return page.Elem().Interface().(Page), err
}
<|endoftext|>


<commit_before>// +build linux

package zfs

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/graphdriver"
	"github.com/docker/docker/pkg/mount"
	"github.com/docker/docker/pkg/parsers"
	zfs "github.com/mistifyio/go-zfs"
)

type ZfsOptions struct {
	fsName    string
	mountPath string
}

func init() {
	graphdriver.Register("zfs", Init)
}

type Logger struct{}

func (*Logger) Log(cmd []string) {
	log.Debugf("[zfs] %s", strings.Join(cmd, " "))
}

func Init(base string, opt []string) (graphdriver.Driver, error) {
	var err error
	options, err := parseOptions(opt)
	if err != nil {
		return nil, err
	}
	options.mountPath = base

	rootdir := path.Dir(base)

	if options.fsName == "" {
		err = checkRootdirFs(rootdir)
		if err != nil {
			return nil, err
		}
	}

	if _, err := exec.LookPath("zfs"); err != nil {
		return nil, fmt.Errorf("zfs command is not available: %v", err)
	}

	file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
	if err != nil {
		return nil, fmt.Errorf("cannot open /dev/zfs: %v", err)
	}
	defer file.Close()

	if options.fsName == "" {
		options.fsName, err = lookupZfsDataset(rootdir)
		if err != nil {
			return nil, err
		}
	}

	zfs.SetLogger(new(Logger))

	filesystems, err := zfs.Filesystems(options.fsName)
	if err != nil {
		return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err)
	}

	filesystemsCache := make(map[string]bool, len(filesystems))
	var rootDataset *zfs.Dataset
	for _, fs := range filesystems {
		if fs.Name == options.fsName {
			rootDataset = fs
		}
		filesystemsCache[fs.Name] = true
	}

	if rootDataset == nil {
		return nil, fmt.Errorf("BUG: zfs get all -t filesystems -rHp '%s' should contain '%s'", options.fsName, options.fsName)
	}

	d := &Driver{
		dataset:          rootDataset,
		options:          options,
		filesystemsCache: filesystemsCache,
	}
	return graphdriver.NaiveDiffDriver(d), nil
}

func parseOptions(opt []string) (ZfsOptions, error) {
	var options ZfsOptions
	options.fsName = ""
	for _, option := range opt {
		key, val, err := parsers.ParseKeyValueOpt(option)
		if err != nil {
			return options, err
		}
		key = strings.ToLower(key)
		switch key {
		case "zfs.fsname":
			options.fsName = val
		default:
			return options, fmt.Errorf("Unknown option %s", key)
		}
	}
	return options, nil
}

func checkRootdirFs(rootdir string) error {
	var buf syscall.Statfs_t
	if err := syscall.Statfs(rootdir, &buf); err != nil {
		return fmt.Errorf("Failed to access '%s': %s", rootdir, err)
	}

	if graphdriver.FsMagic(buf.Type) != graphdriver.FsMagicZfs {
		log.Debugf("[zfs] no zfs dataset found for rootdir '%s'", rootdir)
		return graphdriver.ErrPrerequisites
	}
	return nil
}

func lookupZfsDataset(rootdir string) (string, error) {
	var stat syscall.Stat_t
	if err := syscall.Stat(rootdir, &stat); err != nil {
		return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
	}
	wantedDev := stat.Dev

	mounts, err := mount.GetMounts()
	if err != nil {
		return "", err
	}
	for _, m := range mounts {
		if err := syscall.Stat(m.Mountpoint, &stat); err != nil {
			log.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
			continue // may fail on fuse file systems
		}

		if stat.Dev == wantedDev && m.Fstype == "zfs" {
			return m.Source, nil
		}
	}

	return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
}

type Driver struct {
	dataset          *zfs.Dataset
	options          ZfsOptions
	sync.Mutex       // protects filesystem cache against concurrent access
	filesystemsCache map[string]bool
}

func (d *Driver) String() string {
	return "zfs"
}

func (d *Driver) Cleanup() error {
	return nil
}

func (d *Driver) Status() [][2]string {
	parts := strings.Split(d.dataset.Name, "/")
	pool, err := zfs.GetZpool(parts[0])

	var poolName, poolHealth string
	if err == nil {
		poolName = pool.Name
		poolHealth = pool.Health
	} else {
		poolName = fmt.Sprintf("error while getting pool information %v", err)
		poolHealth = "not available"
	}

	quota := "no"
	if d.dataset.Quota != 0 {
		quota = strconv.FormatUint(d.dataset.Quota, 10)
	}

	return [][2]string{
		{"Zpool", poolName},
		{"Zpool Health", poolHealth},
		{"Parent Dataset", d.dataset.Name},
		{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
		{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
		{"Parent Quota", quota},
		{"Compression", d.dataset.Compression},
	}
}

func (d *Driver) cloneFilesystem(name, parentName string) error {
	snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
	parentDataset := zfs.Dataset{Name: parentName}
	snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
	if err != nil {
		return err
	}

	_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
	if err == nil {
		d.Lock()
		d.filesystemsCache[name] = true
		d.Unlock()
	}

	if err != nil {
		snapshot.Destroy(zfs.DestroyDeferDeletion)
		return err
	}
	return snapshot.Destroy(zfs.DestroyDeferDeletion)
}

func (d *Driver) ZfsPath(id string) string {
	return d.options.fsName + "/" + id
}

func (d *Driver) MountPath(id string) string {
	return path.Join(d.options.mountPath, "graph", id)
}

func (d *Driver) Create(id string, parent string) error {
	err := d.create(id, parent)
	if err == nil {
		return nil
	}
	if zfsError, ok := err.(*zfs.Error); ok {
		if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
			return err
		}
		// aborted build -> cleanup
	} else {
		return err
	}

	dataset := zfs.Dataset{Name: d.ZfsPath(id)}
	if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
		return err
	}

	// retry
	return d.create(id, parent)
}

func (d *Driver) create(id, parent string) error {
	name := d.ZfsPath(id)
	if parent == "" {
		mountoptions := map[string]string{"mountpoint": "legacy"}
		fs, err := zfs.CreateFilesystem(name, mountoptions)
		if err == nil {
			d.Lock()
			d.filesystemsCache[fs.Name] = true
			d.Unlock()
		}
		return err
	}
	return d.cloneFilesystem(name, d.ZfsPath(parent))
}

func (d *Driver) Remove(id string) error {
	name := d.ZfsPath(id)
	dataset := zfs.Dataset{Name: name}
	err := dataset.Destroy(zfs.DestroyRecursive)
	if err == nil {
		d.Lock()
		delete(d.filesystemsCache, name)
		d.Unlock()
	}
	return err
}

func (d *Driver) Get(id, mountLabel string) (string, error) {
	mountpoint := d.MountPath(id)
	filesystem := d.ZfsPath(id)
	log.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, mountLabel)

	// Create the target directories if they don't exist
	if err := os.MkdirAll(mountpoint, 0755); err != nil && !os.IsExist(err) {
		return "", err
	}

	err := mount.Mount(filesystem, mountpoint, "zfs", mountLabel)
	if err != nil {
		return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
	}

	return mountpoint, nil
}

func (d *Driver) Put(id string) error {
	mountpoint := d.MountPath(id)
	log.Debugf(`[zfs] unmount("%s")`, mountpoint)

	if err := mount.Unmount(mountpoint); err != nil {
		return fmt.Errorf("error unmounting to %s: %v", mountpoint, err)
	}
	return nil
}

func (d *Driver) Exists(id string) bool {
	return d.filesystemsCache[d.ZfsPath(id)] == true
}
<commit_msg>zfs: correctly apply selinux context<commit_after>// +build linux

package zfs

import (
	"fmt"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/graphdriver"
	"github.com/docker/docker/pkg/mount"
	"github.com/docker/docker/pkg/parsers"
	"github.com/docker/libcontainer/label"
	zfs "github.com/mistifyio/go-zfs"
)

type ZfsOptions struct {
	fsName    string
	mountPath string
}

func init() {
	graphdriver.Register("zfs", Init)
}

type Logger struct{}

func (*Logger) Log(cmd []string) {
	log.Debugf("[zfs] %s", strings.Join(cmd, " "))
}

func Init(base string, opt []string) (graphdriver.Driver, error) {
	var err error
	options, err := parseOptions(opt)
	if err != nil {
		return nil, err
	}
	options.mountPath = base

	rootdir := path.Dir(base)

	if options.fsName == "" {
		err = checkRootdirFs(rootdir)
		if err != nil {
			return nil, err
		}
	}

	if _, err := exec.LookPath("zfs"); err != nil {
		return nil, fmt.Errorf("zfs command is not available: %v", err)
	}

	file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
	if err != nil {
		return nil, fmt.Errorf("cannot open /dev/zfs: %v", err)
	}
	defer file.Close()

	if options.fsName == "" {
		options.fsName, err = lookupZfsDataset(rootdir)
		if err != nil {
			return nil, err
		}
	}

	zfs.SetLogger(new(Logger))

	filesystems, err := zfs.Filesystems(options.fsName)
	if err != nil {
		return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err)
	}

	filesystemsCache := make(map[string]bool, len(filesystems))
	var rootDataset *zfs.Dataset
	for _, fs := range filesystems {
		if fs.Name == options.fsName {
			rootDataset = fs
		}
		filesystemsCache[fs.Name] = true
	}

	if rootDataset == nil {
		return nil, fmt.Errorf("BUG: zfs get all -t filesystems -rHp '%s' should contain '%s'", options.fsName, options.fsName)
	}

	d := &Driver{
		dataset:          rootDataset,
		options:          options,
		filesystemsCache: filesystemsCache,
	}
	return graphdriver.NaiveDiffDriver(d), nil
}

func parseOptions(opt []string) (ZfsOptions, error) {
	var options ZfsOptions
	options.fsName = ""
	for _, option := range opt {
		key, val, err := parsers.ParseKeyValueOpt(option)
		if err != nil {
			return options, err
		}
		key = strings.ToLower(key)
		switch key {
		case "zfs.fsname":
			options.fsName = val
		default:
			return options, fmt.Errorf("Unknown option %s", key)
		}
	}
	return options, nil
}

func checkRootdirFs(rootdir string) error {
	var buf syscall.Statfs_t
	if err := syscall.Statfs(rootdir, &buf); err != nil {
		return fmt.Errorf("Failed to access '%s': %s", rootdir, err)
	}

	if graphdriver.FsMagic(buf.Type) != graphdriver.FsMagicZfs {
		log.Debugf("[zfs] no zfs dataset found for rootdir '%s'", rootdir)
		return graphdriver.ErrPrerequisites
	}
	return nil
}

func lookupZfsDataset(rootdir string) (string, error) {
	var stat syscall.Stat_t
	if err := syscall.Stat(rootdir, &stat); err != nil {
		return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
	}
	wantedDev := stat.Dev

	mounts, err := mount.GetMounts()
	if err != nil {
		return "", err
	}
	for _, m := range mounts {
		if err := syscall.Stat(m.Mountpoint, &stat); err != nil {
			log.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
			continue // may fail on fuse file systems
		}

		if stat.Dev == wantedDev && m.Fstype == "zfs" {
			return m.Source, nil
		}
	}

	return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
}

type Driver struct {
	dataset          *zfs.Dataset
	options          ZfsOptions
	sync.Mutex       // protects filesystem cache against concurrent access
	filesystemsCache map[string]bool
}

func (d *Driver) String() string {
	return "zfs"
}

func (d *Driver) Cleanup() error {
	return nil
}

func (d *Driver) Status() [][2]string {
	parts := strings.Split(d.dataset.Name, "/")
	pool, err := zfs.GetZpool(parts[0])

	var poolName, poolHealth string
	if err == nil {
		poolName = pool.Name
		poolHealth = pool.Health
	} else {
		poolName = fmt.Sprintf("error while getting pool information %v", err)
		poolHealth = "not available"
	}

	quota := "no"
	if d.dataset.Quota != 0 {
		quota = strconv.FormatUint(d.dataset.Quota, 10)
	}

	return [][2]string{
		{"Zpool", poolName},
		{"Zpool Health", poolHealth},
		{"Parent Dataset", d.dataset.Name},
		{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
		{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
		{"Parent Quota", quota},
		{"Compression", d.dataset.Compression},
	}
}

func (d *Driver) cloneFilesystem(name, parentName string) error {
	snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
	parentDataset := zfs.Dataset{Name: parentName}
	snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
	if err != nil {
		return err
	}

	_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
	if err == nil {
		d.Lock()
		d.filesystemsCache[name] = true
		d.Unlock()
	}

	if err != nil {
		snapshot.Destroy(zfs.DestroyDeferDeletion)
		return err
	}
	return snapshot.Destroy(zfs.DestroyDeferDeletion)
}

func (d *Driver) ZfsPath(id string) string {
	return d.options.fsName + "/" + id
}

func (d *Driver) MountPath(id string) string {
	return path.Join(d.options.mountPath, "graph", id)
}

func (d *Driver) Create(id string, parent string) error {
	err := d.create(id, parent)
	if err == nil {
		return nil
	}
	if zfsError, ok := err.(*zfs.Error); ok {
		if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
			return err
		}
		// aborted build -> cleanup
	} else {
		return err
	}

	dataset := zfs.Dataset{Name: d.ZfsPath(id)}
	if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
		return err
	}

	// retry
	return d.create(id, parent)
}

func (d *Driver) create(id, parent string) error {
	name := d.ZfsPath(id)
	if parent == "" {
		mountoptions := map[string]string{"mountpoint": "legacy"}
		fs, err := zfs.CreateFilesystem(name, mountoptions)
		if err == nil {
			d.Lock()
			d.filesystemsCache[fs.Name] = true
			d.Unlock()
		}
		return err
	}
	return d.cloneFilesystem(name, d.ZfsPath(parent))
}

func (d *Driver) Remove(id string) error {
	name := d.ZfsPath(id)
	dataset := zfs.Dataset{Name: name}
	err := dataset.Destroy(zfs.DestroyRecursive)
	if err == nil {
		d.Lock()
		delete(d.filesystemsCache, name)
		d.Unlock()
	}
	return err
}

func (d *Driver) Get(id, mountLabel string) (string, error) {
	mountpoint := d.MountPath(id)
	filesystem := d.ZfsPath(id)
	options := label.FormatMountLabel("", mountLabel)
	log.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)

	// Create the target directories if they don't exist
	if err := os.MkdirAll(mountpoint, 0755); err != nil && !os.IsExist(err) {
		return "", err
	}

	err := mount.Mount(filesystem, mountpoint, "zfs", options)
	if err != nil {
		return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
	}

	return mountpoint, nil
}

func (d *Driver) Put(id string) error {
	mountpoint := d.MountPath(id)
	log.Debugf(`[zfs] unmount("%s")`, mountpoint)

	if err := mount.Unmount(mountpoint); err != nil {
		return fmt.Errorf("error unmounting to %s: %v", mountpoint, err)
	}
	return nil
}

func (d *Driver) Exists(id string) bool {
	return d.filesystemsCache[d.ZfsPath(id)] == true
}
<|endoftext|>


<commit_before>package release

import (
	"context"
	"fmt"
	"io"
	"net/url"
	"reflect"

	"github.com/google/go-querystring/query"

	"golang.org/x/oauth2"

	"github.com/google/go-github/github"
	"github.com/kyma-project/test-infra/development/tools/pkg/common"
)

// GithubAPI exposes functions to interact with Github releases
type GithubAPI interface {
	CreateGithubRelease(ctx context.Context, opts *Options) (*github.RepositoryRelease, *github.Response, error)
	UploadContent(ctx context.Context, releaseID int64, artifactName string, reader io.Reader, size int64) (*github.Response, error)
}

// githubAPIWrapper implements functions to interact with Github releases
type githubAPIWrapper struct {
	githubClient *github.Client
	repoOwner    string
	repoName     string
}

// NewGithubAPI returns implementation of githubAPI
func NewGithubAPI(ctx context.Context, githubAccessToken, repoOwner, repoName string) GithubAPI {

	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: githubAccessToken},
	)

	tc := oauth2.NewClient(ctx, ts)
	githubClient := github.NewClient(tc)

	return &githubAPIWrapper{
		githubClient: githubClient,
		repoOwner:    repoOwner,
		repoName:     repoName,
	}
}

// CreateGithubRelease creates a Github release
func (gaw *githubAPIWrapper) CreateGithubRelease(ctx context.Context, opts *Options) (*github.RepositoryRelease, *github.Response, error) {
	common.Shout("Creating release %s in %s/%s repository", opts.Version, gaw.repoOwner, gaw.repoName)

	input := &github.RepositoryRelease{
		TagName:         &opts.Version,
		TargetCommitish: &opts.TargetCommit,
		Name:            &opts.Version,
		Body:            &opts.Body,
		Prerelease:      &opts.IsPreRelease,
	}

	return gaw.githubClient.Repositories.CreateRelease(ctx, gaw.repoOwner, gaw.repoName, input)
}

// UploadContent creates an asset by uploading a file into a release repository
func (gaw *githubAPIWrapper) UploadContent(ctx context.Context, releaseID int64, artifactName string, reader io.Reader, size int64) (*github.Response, error) {

	common.Shout("Uploading %s artifact", artifactName)

	u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", gaw.repoOwner, gaw.repoName, releaseID)

	opt := &github.UploadOptions{Name: artifactName}

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, err
	}

	req, err := gaw.githubClient.NewUploadRequest(u, reader, size, "")
	if err != nil {
		return nil, err
	}

	asset := new(github.ReleaseAsset)

	return gaw.githubClient.Do(ctx, req, asset)
}

func addOptions(s string, opt interface{}) (string, error) {
	v := reflect.ValueOf(opt)
	if v.Kind() == reflect.Ptr && v.IsNil() {
		return s, nil
	}

	u, err := url.Parse(s)
	if err != nil {
		return s, err
	}

	qs, err := query.Values(opt)
	if err != nil {
		return s, err
	}

	u.RawQuery = qs.Encode()
	return u.String(), nil
}
<commit_msg>Github release: use token authorization (#3996)<commit_after>package release

import (
	"context"
	"fmt"
	"io"
	"net/url"
	"reflect"

	"github.com/google/go-querystring/query"

	"golang.org/x/oauth2"

	"github.com/google/go-github/github"
	"github.com/kyma-project/test-infra/development/tools/pkg/common"
)

// GithubAPI exposes functions to interact with Github releases
type GithubAPI interface {
	CreateGithubRelease(ctx context.Context, opts *Options) (*github.RepositoryRelease, *github.Response, error)
	UploadContent(ctx context.Context, releaseID int64, artifactName string, reader io.Reader, size int64) (*github.Response, error)
}

// githubAPIWrapper implements functions to interact with Github releases
type githubAPIWrapper struct {
	githubClient *github.Client
	repoOwner    string
	repoName     string
}

// NewGithubAPI returns implementation of githubAPI
func NewGithubAPI(ctx context.Context, githubAccessToken, repoOwner, repoName string) GithubAPI {

	ts := oauth2.StaticTokenSource(
		&oauth2.Token{
			AccessToken: githubAccessToken,
			TokenType:   "token",
		},
	)

	tc := oauth2.NewClient(ctx, ts)
	githubClient := github.NewClient(tc)

	return &githubAPIWrapper{
		githubClient: githubClient,
		repoOwner:    repoOwner,
		repoName:     repoName,
	}
}

// CreateGithubRelease creates a Github release
func (gaw *githubAPIWrapper) CreateGithubRelease(ctx context.Context, opts *Options) (*github.RepositoryRelease, *github.Response, error) {
	common.Shout("Creating release %s in %s/%s repository", opts.Version, gaw.repoOwner, gaw.repoName)

	input := &github.RepositoryRelease{
		TagName:         &opts.Version,
		TargetCommitish: &opts.TargetCommit,
		Name:            &opts.Version,
		Body:            &opts.Body,
		Prerelease:      &opts.IsPreRelease,
	}

	return gaw.githubClient.Repositories.CreateRelease(ctx, gaw.repoOwner, gaw.repoName, input)
}

// UploadContent creates an asset by uploading a file into a release repository
func (gaw *githubAPIWrapper) UploadContent(ctx context.Context, releaseID int64, artifactName string, reader io.Reader, size int64) (*github.Response, error) {

	common.Shout("Uploading %s artifact", artifactName)

	u := fmt.Sprintf("repos/%s/%s/releases/%d/assets", gaw.repoOwner, gaw.repoName, releaseID)

	opt := &github.UploadOptions{Name: artifactName}

	u, err := addOptions(u, opt)
	if err != nil {
		return nil, err
	}

	req, err := gaw.githubClient.NewUploadRequest(u, reader, size, "")
	if err != nil {
		return nil, err
	}

	asset := new(github.ReleaseAsset)

	return gaw.githubClient.Do(ctx, req, asset)
}

func addOptions(s string, opt interface{}) (string, error) {
	v := reflect.ValueOf(opt)
	if v.Kind() == reflect.Ptr && v.IsNil() {
		return s, nil
	}

	u, err := url.Parse(s)
	if err != nil {
		return s, err
	}

	qs, err := query.Values(opt)
	if err != nil {
		return s, err
	}

	u.RawQuery = qs.Encode()
	return u.String(), nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"reflect"
	"testing"
)

type SusiTest struct {
	students map[int][]byte
	courses  map[string][]byte
}

func newSusiTest() (*SusiTest, *Susi) {
	st := new(SusiTest)
	st.students = map[int][]byte{
		11111: []byte(`{"faculty_number":11111,"first_name":"Test","last_name":"One","master":false,"academic_year":1}`),
		22222: []byte(`{"faculty_number":22222,"first_name":"Test","last_name":"Two","master":false,"academic_year":2}`),
		33333: []byte(`{"faculty_number":33333,"first_name":"Test","last_name":"Three","master":false,"academic_year":3}`),
		44444: []byte(`{"faculty_number":44444,"first_name":"Test","last_name":"Four","master":false,"academic_year":4}`),
		55555: []byte(`{"faculty_number":55555,"first_name":"Test","last_name":"Master","master":true,"academic_year":0}`),
	}

	st.courses = map[string][]byte{
		"AR":   []byte(`{"course_name":"Advanced Robotics","course_identifier":"AR","minimum_academic_year":3,"masters_only":false,"available_places":2}`),
		"R101": []byte(`{"course_name":"Robotics 101","course_identifier":"R101","minimum_academic_year":1,"masters_only":false,"available_places":2}`),
		"MO":   []byte(`{"course_name":"Masters Only","course_identifier":"MO","minimum_academic_year":0,"masters_only":true,"available_places":2}`),
		"FC":   []byte(`{"course_name":"Full Course","course_identifier":"FC","minimum_academic_year":0,"masters_only":false,"available_places":0}`),
	}

	return st, NewSusi()
}

func (st *SusiTest) AddStudents(s *Susi, fns ...int) error {
	for _, fn := range fns {
		err := s.AddStudent(st.students[fn])
		if err != nil {
			return err
		}
	}
	return nil
}

func (st *SusiTest) AddCourses(s *Susi, identifiers ...string) error {
	for _, identifier := range identifiers {
		err := s.AddCourse(st.courses[identifier])
		if err != nil {
			return err
		}
	}
	return nil
}

func (st *SusiTest) Enroll(s *Susi, fn int, identifier string) error {
	payload := []byte(fmt.Sprintf("{\"faculty_number\":%d,\"course_identifier\":\"%s\"}", fn, identifier))
	return s.Enroll(payload)
}

// Errors
func (st *SusiTest) studentCannotEnrollError(student *Student, course *Course) string {
	return fmt.Sprintf("%s %s не покрива изискванията за %s!", student.FirstName, student.LastName, course.CourseName)
}

func (st *SusiTest) studentNotFoundError(fn int) string {
	return fmt.Sprintf("Няма студент с факултетен номер %d!", fn)
}

func (st *SusiTest) studentAlreadyExistsError(fn int) string {
	return fmt.Sprintf("Студент с факултетен номер %d вече съществува!", fn)
}

func (st *SusiTest) courseNotFoundError(identifier string) string {
	return fmt.Sprintf("Няма курс с identifier - %s!", identifier)
}

func (st *SusiTest) courseAlreadyExistsError(identifier string) string {
	return fmt.Sprintf("Курс с identifier %s вече съществува!", identifier)
}

func (st *SusiTest) courseIsFullError(identifier string) string {
	return fmt.Sprintf("Няма свободни места за курс с identifier - %s!", identifier)
}

func (st *SusiTest) enrollmentAlreadyExistsError(fn int, identifier string) string {
	return fmt.Sprintf("Студент с факултетен номер %d е вече записан за курс с identifier %s!", fn, identifier)
}

func (st *SusiTest) enrollmentNotFoundError(fn int, identifier string) string {
	return fmt.Sprintf("Студент с факултетен номер %d не е записан за курса с identifier %s!", fn, identifier)
}

// Tests

func TestAddStudent(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}
}

func TestFindMissingStudent(t *testing.T) {
	st, s := newSusiTest()
	_, err := s.FindStudent(22222)

	if err == nil {
		t.Error("Expected to recieve an error when getting an missing student!")
	}

	got := err.Error()
	expected := st.studentNotFoundError(22222)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestAddCourse(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddCourses(s, "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}
}

func TestEnroll(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 22222)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "AR", "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll in a course, recieved: %s", err.Error())
	}

	err = st.Enroll(s, 22222, "R101")
	if err != nil {
		t.Errorf("Failed to enroll in a course, recieved: %s", err.Error())
	}
}

func TestEnrollMoreThanAvailablePlaces(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 22222, 33333)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the first student, got: %s", err.Error())
	}

	err = st.Enroll(s, 22222, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the second student, got: %s", err.Error())
	}

	err = st.Enroll(s, 33333, "R101")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling the third student!")
	}

	got := err.Error()
	expected := st.courseIsFullError("R101")
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollTwiceInTheSameCourseWithTheSameUser(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the first time, got: %s", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling twise in the same course with the same user!")
	}

	got := err.Error()
	expected := st.enrollmentAlreadyExistsError(11111, "R101")
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollWhenTheRequirementsAreNotMet(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "AR")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling in a course where the student doesn't meet the requirements!")
	}

	student, _ := s.FindStudent(11111)
	course, _ := s.FindCourse("AR")

	got := err.Error()
	expected := st.studentCannotEnrollError(student, course)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollInMasterOnlyCourse(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 55555)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "MO", "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 55555, "MO")
	if err != nil {
		t.Errorf("Failed to enroll in a master only course when the student is a master, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "MO")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling in a master only course where the student is not a master!")
	}

	student, _ := s.FindStudent(11111)
	course, _ := s.FindCourse("MO")

	got := err.Error()
	expected := st.studentCannotEnrollError(student, course)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestStudentImplementStringer(t *testing.T) {
	st, s := newSusiTest()
	_ = st.AddStudents(s, 11111, 22222)
	student, _ := s.FindStudent(11111)

	if reflect.TypeOf(student).Elem().Implements(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) {
		t.Error("Student doesn't implement Stringer!")
	}

	got := student.String()
	expected := "11111 Test One"
	if got != expected {
		t.Errorf("Student#String failed! Expected: %s, got: %s", expected, got)
	}
}

func TestCourseImplementStringer(t *testing.T) {
	st, s := newSusiTest()
	_ = st.AddCourses(s, "AR", "R101")
	course, _ := s.FindCourse("AR")

	if reflect.TypeOf(course).Elem().Implements(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) {
		t.Error("Course doesn't implement Stringer!")
	}

	got := course.String()
	expected := "AR Advanced Robotics"
	if got != expected {
		t.Errorf("Course#String failed! Expected: %s, got: %s", expected, got)
	}
}
<commit_msg>Add test for SusiError.<commit_after>package main

import (
	"fmt"
	"reflect"
	"testing"
)

type SusiTest struct {
	students map[int][]byte
	courses  map[string][]byte
}

func newSusiTest() (*SusiTest, *Susi) {
	st := new(SusiTest)
	st.students = map[int][]byte{
		11111: []byte(`{"faculty_number":11111,"first_name":"Test","last_name":"One","master":false,"academic_year":1}`),
		22222: []byte(`{"faculty_number":22222,"first_name":"Test","last_name":"Two","master":false,"academic_year":2}`),
		33333: []byte(`{"faculty_number":33333,"first_name":"Test","last_name":"Three","master":false,"academic_year":3}`),
		44444: []byte(`{"faculty_number":44444,"first_name":"Test","last_name":"Four","master":false,"academic_year":4}`),
		55555: []byte(`{"faculty_number":55555,"first_name":"Test","last_name":"Master","master":true,"academic_year":0}`),
	}

	st.courses = map[string][]byte{
		"AR":   []byte(`{"course_name":"Advanced Robotics","course_identifier":"AR","minimum_academic_year":3,"masters_only":false,"available_places":2}`),
		"R101": []byte(`{"course_name":"Robotics 101","course_identifier":"R101","minimum_academic_year":1,"masters_only":false,"available_places":2}`),
		"MO":   []byte(`{"course_name":"Masters Only","course_identifier":"MO","minimum_academic_year":0,"masters_only":true,"available_places":2}`),
		"FC":   []byte(`{"course_name":"Full Course","course_identifier":"FC","minimum_academic_year":0,"masters_only":false,"available_places":0}`),
	}

	return st, NewSusi()
}

func (st *SusiTest) AddStudents(s *Susi, fns ...int) error {
	for _, fn := range fns {
		err := s.AddStudent(st.students[fn])
		if err != nil {
			return err
		}
	}
	return nil
}

func (st *SusiTest) AddCourses(s *Susi, identifiers ...string) error {
	for _, identifier := range identifiers {
		err := s.AddCourse(st.courses[identifier])
		if err != nil {
			return err
		}
	}
	return nil
}

func (st *SusiTest) Enroll(s *Susi, fn int, identifier string) error {
	payload := []byte(fmt.Sprintf("{\"faculty_number\":%d,\"course_identifier\":\"%s\"}", fn, identifier))
	return s.Enroll(payload)
}

// Errors
func (st *SusiTest) studentCannotEnrollError(student *Student, course *Course) string {
	return fmt.Sprintf("%s %s не покрива изискванията за %s!", student.FirstName, student.LastName, course.CourseName)
}

func (st *SusiTest) studentNotFoundError(fn int) string {
	return fmt.Sprintf("Няма студент с факултетен номер %d!", fn)
}

func (st *SusiTest) studentAlreadyExistsError(fn int) string {
	return fmt.Sprintf("Студент с факултетен номер %d вече съществува!", fn)
}

func (st *SusiTest) courseNotFoundError(identifier string) string {
	return fmt.Sprintf("Няма курс с identifier - %s!", identifier)
}

func (st *SusiTest) courseAlreadyExistsError(identifier string) string {
	return fmt.Sprintf("Курс с identifier %s вече съществува!", identifier)
}

func (st *SusiTest) courseIsFullError(identifier string) string {
	return fmt.Sprintf("Няма свободни места за курс с identifier - %s!", identifier)
}

func (st *SusiTest) enrollmentAlreadyExistsError(fn int, identifier string) string {
	return fmt.Sprintf("Студент с факултетен номер %d е вече записан за курс с identifier %s!", fn, identifier)
}

func (st *SusiTest) enrollmentNotFoundError(fn int, identifier string) string {
	return fmt.Sprintf("Студент с факултетен номер %d не е записан за курса с identifier %s!", fn, identifier)
}

// Tests

func TestAddStudent(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}
}

func TestFindMissingStudent(t *testing.T) {
	st, s := newSusiTest()
	_, err := s.FindStudent(22222)

	if err == nil {
		t.Error("Expected to recieve an error when getting an missing student!")
	}

	got := err.Error()
	expected := st.studentNotFoundError(22222)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestAddCourse(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddCourses(s, "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}
}

func TestEnroll(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 22222)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "AR", "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll in a course, recieved: %s", err.Error())
	}

	err = st.Enroll(s, 22222, "R101")
	if err != nil {
		t.Errorf("Failed to enroll in a course, recieved: %s", err.Error())
	}
}

func TestEnrollMoreThanAvailablePlaces(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 22222, 33333)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the first student, got: %s", err.Error())
	}

	err = st.Enroll(s, 22222, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the second student, got: %s", err.Error())
	}

	err = st.Enroll(s, 33333, "R101")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling the third student!")
	}

	got := err.Error()
	expected := st.courseIsFullError("R101")
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollTwiceInTheSameCourseWithTheSameUser(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "FC")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err != nil {
		t.Errorf("Failed to enroll the first time, got: %s", err.Error())
	}

	err = st.Enroll(s, 11111, "R101")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling twise in the same course with the same user!")
	}

	got := err.Error()
	expected := st.enrollmentAlreadyExistsError(11111, "R101")
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollWhenTheRequirementsAreNotMet(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "R101", "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "AR")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling in a course where the student doesn't meet the requirements!")
	}

	student, _ := s.FindStudent(11111)
	course, _ := s.FindCourse("AR")

	got := err.Error()
	expected := st.studentCannotEnrollError(student, course)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestEnrollInMasterOnlyCourse(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 55555)
	if err != nil {
		t.Errorf("Failed to add a student, recieved: %s!", err.Error())
	}

	err = st.AddCourses(s, "MO", "AR")
	if err != nil {
		t.Errorf("Failed to add a course, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 55555, "MO")
	if err != nil {
		t.Errorf("Failed to enroll in a master only course when the student is a master, recieved: %s!", err.Error())
	}

	err = st.Enroll(s, 11111, "MO")
	if err == nil {
		t.Error("Expected to recieve an error when enrolling in a master only course where the student is not a master!")
	}

	student, _ := s.FindStudent(11111)
	course, _ := s.FindCourse("MO")

	got := err.Error()
	expected := st.studentCannotEnrollError(student, course)
	if got != expected {
		t.Errorf("Expected: %s, got: %s", expected, got)
	}
}

func TestStudentImplementStringer(t *testing.T) {
	st, s := newSusiTest()
	_ = st.AddStudents(s, 11111, 22222)
	student, _ := s.FindStudent(11111)

	if reflect.TypeOf(student).Elem().Implements(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) {
		t.Error("Student doesn't implement Stringer!")
	}

	got := student.String()
	expected := "11111 Test One"
	if got != expected {
		t.Errorf("Student#String failed! Expected: %s, got: %s", expected, got)
	}
}

func TestCourseImplementStringer(t *testing.T) {
	st, s := newSusiTest()
	_ = st.AddCourses(s, "AR", "R101")
	course, _ := s.FindCourse("AR")

	if reflect.TypeOf(course).Elem().Implements(reflect.TypeOf((*fmt.Stringer)(nil)).Elem()) {
		t.Error("Course doesn't implement Stringer!")
	}

	got := course.String()
	expected := "AR Advanced Robotics"
	if got != expected {
		t.Errorf("Course#String failed! Expected: %s, got: %s", expected, got)
	}
}

func TestSusiErrorOnEnrollment(t *testing.T) {
	st, s := newSusiTest()
	err := st.AddStudents(s, 11111, 55555)
	err = st.AddCourses(s, "MO", "AR")
	err = st.Enroll(s, 11111, "MO")
	if err == nil {
		t.Error("Expected to recieve an error")
	}

	student, _ := s.FindStudent(11111)
	course, _ := s.FindCourse("MO")

	errorType := reflect.TypeOf(err).String()
	if errorType != "*main.SusiError" && errorType != "*SusiError" {
		t.Errorf("Expected error to be *main.SusiError, but was: %s", errorType)
	}

	susiErr := err.(*SusiError)

	if susiErr.Course != course {
		t.Errorf("Expected susiErr.Course to be %V, but was %V", course, susiErr.Course)
	}

	if susiErr.Student != student {
		t.Errorf("Expected susiErr.Student to be %V, but was %V", student, susiErr.Student)
	}
}
<|endoftext|>


<commit_before>package pcre2_test

import (
	"regexp"
	"testing"

	"github.com/lestrrat/go-pcre2"
)

type matchStringer interface {
	MatchString(string) bool
}

func benchMatchString(b *testing.B, re matchStringer) {
	patterns := []string{`Hello World!`, `Hello Friend!`, `Hello 友達!`}
	for _, pat := range patterns {
		if !re.MatchString(pat) {
			b.Errorf("Expected to match, failed")
			return
		}
	}

	patterns = []string{`Goodbye World!`, `Hell no!`, `HelloWorld!`}
	for _, pat := range patterns {
		if re.MatchString(pat) {
			b.Errorf("Expected to NOT match, matched")
			return
		}
	}
}

func BenchmarkGoRegexpMatch(b *testing.B) {
	for i := 0; i < b.N; i++ {
		re, err := regexp.Compile(`^Hello (.+)!$`)
		if err != nil {
			b.Errorf("compile failed: %s", err)
			return
		}
		benchMatchString(b, re)
	}
}

func BenchmarkPCRE2RegexpMatch(b *testing.B) {
	for i := 0; i < b.N; i++ {
		re, err := pcre2.Compile(`^Hello (.+)!$`)
		if err != nil {
			b.Errorf("compile failed: %s", err)
			return
		}
		benchMatchString(b, re)
	}
}
<commit_msg>encapsulate match calls in a function<commit_after>package pcre2_test

import (
	"regexp"
	"testing"

	"github.com/lestrrat/go-pcre2"
)

type matchStringer interface {
	MatchString(string) bool
}

func benchMatchString(b *testing.B, re matchStringer) {
	patterns := []string{`Hello World!`, `Hello Friend!`, `Hello 友達!`}
	for _, pat := range patterns {
		if !re.MatchString(pat) {
			b.Errorf("Expected to match, failed")
			return
		}
	}

	patterns = []string{`Goodbye World!`, `Hell no!`, `HelloWorld!`}
	for _, pat := range patterns {
		if re.MatchString(pat) {
			b.Errorf("Expected to NOT match, matched")
			return
		}
	}
}

func BenchmarkGoRegexpMatch(b *testing.B) {
	benchf := func () {
		// Forcing a function call here so that we have chance to
		// run garbage collection for each iteration
		re, err := regexp.Compile(`^Hello (.+)!$`)
		if err != nil {
			b.Errorf("compile failed: %s", err)
			return
		}
		benchMatchString(b, re)
	}

	for i := 0; i < b.N; i++ {
		benchf()
	}
}

func BenchmarkPCRE2RegexpMatch(b *testing.B) {
	benchf := func() {
		// Forcing a function call here so that we have chance to
		// run garbage collection for each iteration
		re, err := pcre2.Compile(`^Hello (.+)!$`)
		if err != nil {
			b.Errorf("compile failed: %s", err)
			return
		}
		defer re.Free()
		benchMatchString(b, re)
	}

	for i := 0; i < b.N; i++ {
		benchf()
	}
}
<|endoftext|>