<commit_before>package prompt

import (
	"bytes"
	"strconv"
)

// VT100Writer generates VT100 escape sequences.
type VT100Writer struct {
	buffer []byte
}

// WriteRaw to write raw byte array
func (w *VT100Writer) WriteRaw(data []byte) {
	w.buffer = append(w.buffer, data...)
	return
}

// Write to write safety byte array by removing control sequences.
func (w *VT100Writer) Write(data []byte) {
	w.WriteRaw(bytes.Replace(data, []byte{0x1b}, []byte{'?'}, -1))
	return
}

// WriteRawStr to write raw string
func (w *VT100Writer) WriteRawStr(data string) {
	w.WriteRaw([]byte(data))
	return
}

// WriteStr to write safety string by removing control sequences.
func (w *VT100Writer) WriteStr(data string) {
	w.Write([]byte(data))
	return
}

/* Erase */

// EraseScreen erases the screen with the background colour and moves the cursor to home.
func (w *VT100Writer) EraseScreen() {
	w.WriteRaw([]byte{0x1b, '[', '2', 'J'})
	return
}

// EraseUp erases the screen from the current line up to the top of the screen.
func (w *VT100Writer) EraseUp() {
	w.WriteRaw([]byte{0x1b, '[', '1', 'J'})
	return
}

// EraseDown erases the screen from the current line down to the bottom of the screen.
func (w *VT100Writer) EraseDown() {
	w.WriteRaw([]byte{0x1b, '[', 'J'})
	return
}

// EraseStartOfLine erases from the current cursor position to the start of the current line.
func (w *VT100Writer) EraseStartOfLine() {
	w.WriteRaw([]byte{0x1b, '[', '1', 'K'})
	return
}

// EraseEndOfLine erases from the current cursor position to the end of the current line.
func (w *VT100Writer) EraseEndOfLine() {
	w.WriteRaw([]byte{0x1b, '[', 'K'})
	return
}

// EraseLine erases the entire current line.
func (w *VT100Writer) EraseLine() {
	w.WriteRaw([]byte{0x1b, '[', '2', 'K'})
	return
}

/* Cursor */

// ShowCursor stops blinking cursor and show.
func (w *VT100Writer) ShowCursor() {
	w.WriteRaw([]byte{0x1b, '[', '?', '1', '2', 'l', 0x1b, '[', '?', '2', '5', 'h'})
}

// HideCursor hides cursor.
func (w *VT100Writer) HideCursor() {
	w.WriteRaw([]byte{0x1b, '[', '?', '2', '5', 'l'})
	return
}

// CursorGoTo sets the cursor position where subsequent text will begin.
func (w *VT100Writer) CursorGoTo(row, col int) {
	if row == 0 && col == 0 {
		// If no row/column parameters are provided (ie. <ESC>[H), the cursor will move to the home position.
		w.WriteRaw([]byte{0x1b, '[', 'H'})
		return
	}
	r := strconv.Itoa(row)
	c := strconv.Itoa(col)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(r))
	w.WriteRaw([]byte{';'})
	w.WriteRaw([]byte(c))
	w.WriteRaw([]byte{'H'})
	return
}

// CursorUp moves the cursor up by 'n' rows; the default count is 1.
func (w *VT100Writer) CursorUp(n int) {
	if n == 0 {
		return
	} else if n < 0 {
		w.CursorDown(-n)
		return
	}
	s := strconv.Itoa(n)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(s))
	w.WriteRaw([]byte{'A'})
	return
}

// CursorDown moves the cursor down by 'n' rows; the default count is 1.
func (w *VT100Writer) CursorDown(n int) {
	if n == 0 {
		return
	} else if n < 0 {
		w.CursorUp(-n)
		return
	}
	s := strconv.Itoa(n)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(s))
	w.WriteRaw([]byte{'B'})
	return
}

// CursorForward moves the cursor forward by 'n' columns; the default count is 1.
func (w *VT100Writer) CursorForward(n int) {
	if n == 0 {
		return
	} else if n < 0 {
		w.CursorBackward(-n)
		return
	}
	s := strconv.Itoa(n)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(s))
	w.WriteRaw([]byte{'C'})
	return
}

// CursorBackward moves the cursor backward by 'n' columns; the default count is 1.
func (w *VT100Writer) CursorBackward(n int) {
	if n == 0 {
		return
	} else if n < 0 {
		w.CursorForward(-n)
		return
	}
	s := strconv.Itoa(n)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(s))
	w.WriteRaw([]byte{'D'})
	return
}

// AskForCPR asks for a cursor position report (CPR).
func (w *VT100Writer) AskForCPR() {
	// CPR: Cursor Position Request.
	w.WriteRaw([]byte{0x1b, '[', '6', 'n'})
	return
}

// SaveCursor saves current cursor position.
func (w *VT100Writer) SaveCursor() {
	w.WriteRaw([]byte{0x1b, '[', 's'})
	return
}

// UnSaveCursor restores cursor position after a Save Cursor.
func (w *VT100Writer) UnSaveCursor() {
	w.WriteRaw([]byte{0x1b, '[', 'u'})
	return
}

/* Scrolling */

// ScrollDown scrolls display down one line.
func (w *VT100Writer) ScrollDown() {
	w.WriteRaw([]byte{0x1b, 'D'})
	return
}

// ScrollUp scroll display up one line.
func (w *VT100Writer) ScrollUp() {
	w.WriteRaw([]byte{0x1b, 'M'})
	return
}

/* Title */

// SetTitle sets a title of terminal window.
func (w *VT100Writer) SetTitle(title string) {
	titleBytes := []byte(title)
	patterns := []struct {
		from []byte
		to   []byte
	}{
		{
			from: []byte{0x13},
			to:   []byte{},
		},
		{
			from: []byte{0x07},
			to:   []byte{},
		},
	}
	for i := range patterns {
		titleBytes = bytes.Replace(titleBytes, patterns[i].from, patterns[i].to, -1)
	}

	w.WriteRaw([]byte{0x1b, ']', '2', ';'})
	w.WriteRaw(titleBytes)
	w.WriteRaw([]byte{0x07})
	return
}

// ClearTitle clears a title of terminal window.
func (w *VT100Writer) ClearTitle() {
	w.WriteRaw([]byte{0x1b, ']', '2', ';', 0x07})
	return
}

/* Font */

// SetColor sets text and background colors. and specify whether text is bold.
func (w *VT100Writer) SetColor(fg, bg Color, bold bool) {
	if bold {
		w.SetDisplayAttributes(fg, bg, DisplayBold)
	} else {
		w.SetDisplayAttributes(fg, bg, DisplayDefaultFont)
	}
	return
}

// SetDisplayAttributes to set VT100 display attributes.
func (w *VT100Writer) SetDisplayAttributes(fg, bg Color, attrs ...DisplayAttribute) {
	w.WriteRaw([]byte{0x1b, '['}) // control sequence introducer
	defer w.WriteRaw([]byte{'m'}) // final character

	var separator byte = ';'
	for i := range attrs {
		p, ok := displayAttributeParameters[attrs[i]]
		if !ok {
			continue
		}
		w.WriteRaw(p)
		w.WriteRaw([]byte{separator})
	}

	f, ok := foregroundANSIColors[fg]
	if !ok {
		f = foregroundANSIColors[DefaultColor]
	}
	w.WriteRaw(f)
	w.WriteRaw([]byte{separator})
	b, ok := backgroundANSIColors[bg]
	if !ok {
		b = backgroundANSIColors[DefaultColor]
	}
	w.WriteRaw(b)
	return
}

var displayAttributeParameters = map[DisplayAttribute][]byte{
	DisplayReset:        {'0'},
	DisplayBold:         {'1'},
	DisplayLowIntensity: {'2'},
	DisplayItalic:       {'3'},
	DisplayUnderline:    {'4'},
	DisplayBlink:        {'5'},
	DisplayRapidBlink:   {'6'},
	DisplayReverse:      {'7'},
	DisplayInvisible:    {'8'},
	DisplayCrossedOut:   {'9'},
	DisplayDefaultFont:  {'1', '0'},
}

var foregroundANSIColors = map[Color][]byte{
	DefaultColor: {'3', '9'},

	// Low intensity.
	Black:     {'3', '0'},
	DarkRed:   {'3', '1'},
	DarkGreen: {'3', '2'},
	Brown:     {'3', '3'},
	DarkBlue:  {'3', '4'},
	Purple:    {'3', '5'},
	Cyan:      {'3', '6'},
	LightGray: {'3', '7'},

	// High intensity.
	DarkGray:  {'9', '0'},
	Red:       {'9', '1'},
	Green:     {'9', '2'},
	Yellow:    {'9', '3'},
	Blue:      {'9', '4'},
	Fuchsia:   {'9', '5'},
	Turquoise: {'9', '6'},
	White:     {'9', '7'},
}

var backgroundANSIColors = map[Color][]byte{
	DefaultColor: {'4', '9'},

	// Low intensity.
	Black:     {'4', '0'},
	DarkRed:   {'4', '1'},
	DarkGreen: {'4', '2'},
	Brown:     {'4', '3'},
	DarkBlue:  {'4', '4'},
	Purple:    {'4', '5'},
	Cyan:      {'4', '6'},
	LightGray: {'4', '7'},

	// High intensity
	DarkGray:  {'1', '0', '0'},
	Red:       {'1', '0', '1'},
	Green:     {'1', '0', '2'},
	Yellow:    {'1', '0', '3'},
	Blue:      {'1', '0', '4'},
	Fuchsia:   {'1', '0', '5'},
	Turquoise: {'1', '0', '6'},
	White:     {'1', '0', '7'},
}
<commit_msg>Fix resetting display attributes on VT100 output<commit_after>package prompt

import (
	"bytes"
	"strconv"
)

// VT100Writer generates VT100 escape sequences.
type VT100Writer struct {
	buffer []byte
}

// WriteRaw to write raw byte array
func (w *VT100Writer) WriteRaw(data []byte) {
	w.buffer = append(w.buffer, data...)
	return
}

// Write to write safety byte array by removing control sequences.
func (w *VT100Writer) Write(data []byte) {
	w.WriteRaw(bytes.Replace(data, []byte{0x1b}, []byte{'?'}, -1))
	return
}

// WriteRawStr to write raw string
func (w *VT100Writer) WriteRawStr(data string) {
	w.WriteRaw([]byte(data))
	return
}

// WriteStr to write safety string by removing control sequences.
func (w *VT100Writer) WriteStr(data string) {
	w.Write([]byte(data))
	return
}

/* Erase */

// EraseScreen erases the screen with the background colour and moves the cursor to home.
func (w *VT100Writer) EraseScreen() {
	w.WriteRaw([]byte{0x1b, '[', '2', 'J'})
	return
}

// EraseUp erases the screen from the current line up to the top of the screen.
func (w *VT100Writer) EraseUp() {
	w.WriteRaw([]byte{0x1b, '[', '1', 'J'})
	return
}

// EraseDown erases the screen from the current line down to the bottom of the screen.
func (w *VT100Writer) EraseDown() {
	w.WriteRaw([]byte{0x1b, '[', 'J'})
	return
}

// EraseStartOfLine erases from the current cursor position to the start of the current line.
func (w *VT100Writer) EraseStartOfLine() {
	w.WriteRaw([]byte{0x1b, '[', '1', 'K'})
	return
}

// EraseEndOfLine erases from the current cursor position to the end of the current line.
func (w *VT100Writer) EraseEndOfLine() {
	w.WriteRaw([]byte{0x1b, '[', 'K'})
	return
}

// EraseLine erases the entire current line.
func (w *VT100Writer) EraseLine() {
	w.WriteRaw([]byte{0x1b, '[', '2', 'K'})
	return
}

/* Cursor */

// ShowCursor stops blinking cursor and show.
func (w *VT100Writer) ShowCursor() {
	w.WriteRaw([]byte{0x1b, '[', '?', '1', '2', 'l', 0x1b, '[', '?', '2', '5', 'h'})
}

// HideCursor hides cursor.
func (w *VT100Writer) HideCursor() {
	w.WriteRaw([]byte{0x1b, '[', '?', '2', '5', 'l'})
	return
}

// CursorGoTo sets the cursor position where subsequent text will begin.
func (w *VT100Writer) CursorGoTo(row, col int) {
	if row == 0 && col == 0 {
		// If no row/column parameters are provided (ie. <ESC>[H), the cursor will move to the home position.
		w.WriteRaw([]byte{0x1b, '[', 'H'})
		return
	}
	r := strconv.Itoa(row)
	c := strconv.Itoa(col)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(r))
	w.WriteRaw([]byte{';'})
	w.WriteRaw([]byte(c))
	w.WriteRaw([]byte{'H'})
	return
}

// CursorUp moves the cursor up by 'n' rows; the default count is 1.
func (w *VT100Writer) CursorUp(n int) {
	if n == 0 {
		return
	} else if n < 0 {
		w.CursorDown(-n)
		return
	}
	s := strconv.Itoa(n)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(s))
	w.WriteRaw([]byte{'A'})
	return
}

// CursorDown moves the cursor down by 'n' rows; the default count is 1.
func (w *VT100Writer) CursorDown(n int) {
	if n == 0 {
		return
	} else if n < 0 {
		w.CursorUp(-n)
		return
	}
	s := strconv.Itoa(n)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(s))
	w.WriteRaw([]byte{'B'})
	return
}

// CursorForward moves the cursor forward by 'n' columns; the default count is 1.
func (w *VT100Writer) CursorForward(n int) {
	if n == 0 {
		return
	} else if n < 0 {
		w.CursorBackward(-n)
		return
	}
	s := strconv.Itoa(n)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(s))
	w.WriteRaw([]byte{'C'})
	return
}

// CursorBackward moves the cursor backward by 'n' columns; the default count is 1.
func (w *VT100Writer) CursorBackward(n int) {
	if n == 0 {
		return
	} else if n < 0 {
		w.CursorForward(-n)
		return
	}
	s := strconv.Itoa(n)
	w.WriteRaw([]byte{0x1b, '['})
	w.WriteRaw([]byte(s))
	w.WriteRaw([]byte{'D'})
	return
}

// AskForCPR asks for a cursor position report (CPR).
func (w *VT100Writer) AskForCPR() {
	// CPR: Cursor Position Request.
	w.WriteRaw([]byte{0x1b, '[', '6', 'n'})
	return
}

// SaveCursor saves current cursor position.
func (w *VT100Writer) SaveCursor() {
	w.WriteRaw([]byte{0x1b, '[', 's'})
	return
}

// UnSaveCursor restores cursor position after a Save Cursor.
func (w *VT100Writer) UnSaveCursor() {
	w.WriteRaw([]byte{0x1b, '[', 'u'})
	return
}

/* Scrolling */

// ScrollDown scrolls display down one line.
func (w *VT100Writer) ScrollDown() {
	w.WriteRaw([]byte{0x1b, 'D'})
	return
}

// ScrollUp scroll display up one line.
func (w *VT100Writer) ScrollUp() {
	w.WriteRaw([]byte{0x1b, 'M'})
	return
}

/* Title */

// SetTitle sets a title of terminal window.
func (w *VT100Writer) SetTitle(title string) {
	titleBytes := []byte(title)
	patterns := []struct {
		from []byte
		to   []byte
	}{
		{
			from: []byte{0x13},
			to:   []byte{},
		},
		{
			from: []byte{0x07},
			to:   []byte{},
		},
	}
	for i := range patterns {
		titleBytes = bytes.Replace(titleBytes, patterns[i].from, patterns[i].to, -1)
	}

	w.WriteRaw([]byte{0x1b, ']', '2', ';'})
	w.WriteRaw(titleBytes)
	w.WriteRaw([]byte{0x07})
	return
}

// ClearTitle clears a title of terminal window.
func (w *VT100Writer) ClearTitle() {
	w.WriteRaw([]byte{0x1b, ']', '2', ';', 0x07})
	return
}

/* Font */

// SetColor sets text and background colors. and specify whether text is bold.
func (w *VT100Writer) SetColor(fg, bg Color, bold bool) {
	if bold {
		w.SetDisplayAttributes(fg, bg, DisplayBold)
	} else {
		w.SetDisplayAttributes(fg, bg, DisplayDefaultFont, DisplayReset)
	}
	return
}

// SetDisplayAttributes to set VT100 display attributes.
func (w *VT100Writer) SetDisplayAttributes(fg, bg Color, attrs ...DisplayAttribute) {
	w.WriteRaw([]byte{0x1b, '['}) // control sequence introducer
	defer w.WriteRaw([]byte{'m'}) // final character

	var separator byte = ';'
	for i := range attrs {
		p, ok := displayAttributeParameters[attrs[i]]
		if !ok {
			continue
		}
		w.WriteRaw(p)
		w.WriteRaw([]byte{separator})
	}

	f, ok := foregroundANSIColors[fg]
	if !ok {
		f = foregroundANSIColors[DefaultColor]
	}
	w.WriteRaw(f)
	w.WriteRaw([]byte{separator})
	b, ok := backgroundANSIColors[bg]
	if !ok {
		b = backgroundANSIColors[DefaultColor]
	}
	w.WriteRaw(b)
	return
}

var displayAttributeParameters = map[DisplayAttribute][]byte{
	DisplayReset:        {'0'},
	DisplayBold:         {'1'},
	DisplayLowIntensity: {'2'},
	DisplayItalic:       {'3'},
	DisplayUnderline:    {'4'},
	DisplayBlink:        {'5'},
	DisplayRapidBlink:   {'6'},
	DisplayReverse:      {'7'},
	DisplayInvisible:    {'8'},
	DisplayCrossedOut:   {'9'},
	DisplayDefaultFont:  {'1', '0'},
}

var foregroundANSIColors = map[Color][]byte{
	DefaultColor: {'3', '9'},

	// Low intensity.
	Black:     {'3', '0'},
	DarkRed:   {'3', '1'},
	DarkGreen: {'3', '2'},
	Brown:     {'3', '3'},
	DarkBlue:  {'3', '4'},
	Purple:    {'3', '5'},
	Cyan:      {'3', '6'},
	LightGray: {'3', '7'},

	// High intensity.
	DarkGray:  {'9', '0'},
	Red:       {'9', '1'},
	Green:     {'9', '2'},
	Yellow:    {'9', '3'},
	Blue:      {'9', '4'},
	Fuchsia:   {'9', '5'},
	Turquoise: {'9', '6'},
	White:     {'9', '7'},
}

var backgroundANSIColors = map[Color][]byte{
	DefaultColor: {'4', '9'},

	// Low intensity.
	Black:     {'4', '0'},
	DarkRed:   {'4', '1'},
	DarkGreen: {'4', '2'},
	Brown:     {'4', '3'},
	DarkBlue:  {'4', '4'},
	Purple:    {'4', '5'},
	Cyan:      {'4', '6'},
	LightGray: {'4', '7'},

	// High intensity
	DarkGray:  {'1', '0', '0'},
	Red:       {'1', '0', '1'},
	Green:     {'1', '0', '2'},
	Yellow:    {'1', '0', '3'},
	Blue:      {'1', '0', '4'},
	Fuchsia:   {'1', '0', '5'},
	Turquoise: {'1', '0', '6'},
	White:     {'1', '0', '7'},
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"unsafe"

	"../conio"
	"../lua"
)

type KeyLuaFuncT struct {
	L     lua.Lua
	Chank []byte
}

func getBufferForCallBack(L lua.Lua) (*conio.Buffer, int) {
	if L.GetType(1) != lua.LUA_TTABLE {
		return nil, L.Push(nil, "bindKeyExec: call with : not .")
	}
	L.GetField(1, "buffer")
	if L.GetType(-1) != lua.LUA_TLIGHTUSERDATA {
		return nil, L.Push(nil, "bindKey.Call: invalid object")
	}
	buffer := (*conio.Buffer)(L.ToUserData(-1))
	if buffer == nil {
		return nil, L.Push(nil, "bindKey.Call: invalid member")
	}
	L.Pop(1)
	return buffer, 0
}

func callInsert(L lua.Lua) int {
	buffer, stackRc := getBufferForCallBack(L)
	if buffer == nil {
		return stackRc
	}
	text, textErr := L.ToString(2)
	if textErr != nil {
		return L.Push(nil, textErr)
	}
	buffer.InsertAndRepaint(text)
	return L.Push(true)
}

func callKeyFunc(L lua.Lua) int {
	buffer, stackRc := getBufferForCallBack(L)
	if buffer == nil {
		return stackRc
	}
	key, keyErr := L.ToString(2)
	if keyErr != nil {
		return L.Push(nil, keyErr)
	}
	function, funcErr := conio.GetFunc(key)
	if funcErr != nil {
		return L.Push(nil, funcErr)
	}
	switch function.Call(buffer) {
	case conio.ENTER:
		return L.Push(true, true)
	case conio.ABORT:
		return L.Push(true, false)
	default:
		return L.Push(nil)
	}
}

func callLastWord(L lua.Lua) int {
	this, stack_count := getBufferForCallBack(L)
	if this == nil {
		return stack_count
	}
	word, pos := this.CurrentWord()
	return L.Push(word, pos+1)
}

func callFirstWord(L lua.Lua) int {
	this, stack_count := getBufferForCallBack(L)
	if this == nil {
		return stack_count
	}
	word := conio.QuotedFirstWord(this.String())
	return L.Push(word, 0)
}

func callBoxListing(L lua.Lua) int {
	// stack +1: readline.Buffer
	// stack +2: table
	// stack +3: index or value
	this, stack_count := getBufferForCallBack(L)
	if this == nil {
		return stack_count
	}
	fmt.Print("\n")
	list := make([]string, 0, 100)
	for i := 1; ; i++ {
		L.Push(i)     // to +3
		L.GetTable(2) //
		str, err := L.ToString(3)
		if err != nil {
			fmt.Fprintln(os.Stderr, "boxprint: "+err.Error())
			break
		}
		if str == "" {
			break
		}
		L.Pop(1)
		list = append(list, str)
	}
	conio.BoxPrint(list, os.Stdout)
	this.RepaintAll()
	return 0
}

func (this *KeyLuaFuncT) Call(buffer *conio.Buffer) conio.Result {
	this.L.LoadBufferX("", this.Chank, "b")
	pos := -1
	var text bytes.Buffer
	for i, c := range buffer.Buffer {
		if i >= buffer.Length {
			break
		}
		if i == buffer.Cursor {
			pos = text.Len() + 1
		}
		text.WriteRune(c)
	}
	if pos < 0 {
		pos = text.Len() + 1
	}
	this.L.Push(map[string]interface{}{
		"pos":       pos,
		"text":      text.String(),
		"buffer":    unsafe.Pointer(buffer),
		"call":      callKeyFunc,
		"insert":    callInsert,
		"lastword":  callLastWord,
		"firstword": callFirstWord,
		"boxprint":  callBoxListing,
	})
	if err := this.L.Call(1, 1); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
	switch this.L.GetType(-1) {
	case lua.LUA_TSTRING:
		str, strErr := this.L.ToString(-1)
		if strErr == nil {
			buffer.InsertAndRepaint(str)
		}
	case lua.LUA_TBOOLEAN:
		if !this.L.ToBool(-1) {
			buffer.Buffer = []rune{}
			buffer.Length = 0
		}
		return conio.ENTER
	}
	return conio.CONTINUE
}

func cmdBindKey(L lua.Lua) int {
	key, keyErr := L.ToString(-2)
	if keyErr != nil {
		return L.Push(keyErr)
	}
	key = strings.Replace(strings.ToUpper(key), "-", "_", -1)
	switch L.GetType(-1) {
	case lua.LUA_TFUNCTION:
		chank := L.Dump()
		if err := conio.BindKeyFunc(key, &KeyLuaFuncT{L, chank}); err != nil {
			return L.Push(nil, err)
		} else {
			return L.Push(true)
		}
	default:
		val, valErr := L.ToString(-1)
		if valErr != nil {
			return L.Push(nil, valErr)
		}
		err := conio.BindKeySymbol(key, val)
		if err != nil {
			return L.Push(nil, err)
		} else {
			return L.Push(true)
		}
	}
}
<commit_msg>Fixed nyagos.bindkey pushed non-Pushable map to Lua-stack (#96)<commit_after>package main

import (
	"bytes"
	"fmt"
	"os"
	"strings"
	"unsafe"

	"../conio"
	"../lua"
)

type KeyLuaFuncT struct {
	L     lua.Lua
	Chank []byte
}

func getBufferForCallBack(L lua.Lua) (*conio.Buffer, int) {
	if L.GetType(1) != lua.LUA_TTABLE {
		return nil, L.Push(nil, "bindKeyExec: call with : not .")
	}
	L.GetField(1, "buffer")
	if L.GetType(-1) != lua.LUA_TLIGHTUSERDATA {
		return nil, L.Push(nil, "bindKey.Call: invalid object")
	}
	buffer := (*conio.Buffer)(L.ToUserData(-1))
	if buffer == nil {
		return nil, L.Push(nil, "bindKey.Call: invalid member")
	}
	L.Pop(1)
	return buffer, 0
}

func callInsert(L lua.Lua) int {
	buffer, stackRc := getBufferForCallBack(L)
	if buffer == nil {
		return stackRc
	}
	text, textErr := L.ToString(2)
	if textErr != nil {
		return L.Push(nil, textErr)
	}
	buffer.InsertAndRepaint(text)
	return L.Push(true)
}

func callKeyFunc(L lua.Lua) int {
	buffer, stackRc := getBufferForCallBack(L)
	if buffer == nil {
		return stackRc
	}
	key, keyErr := L.ToString(2)
	if keyErr != nil {
		return L.Push(nil, keyErr)
	}
	function, funcErr := conio.GetFunc(key)
	if funcErr != nil {
		return L.Push(nil, funcErr)
	}
	switch function.Call(buffer) {
	case conio.ENTER:
		return L.Push(true, true)
	case conio.ABORT:
		return L.Push(true, false)
	default:
		return L.Push(nil)
	}
}

func callLastWord(L lua.Lua) int {
	this, stack_count := getBufferForCallBack(L)
	if this == nil {
		return stack_count
	}
	word, pos := this.CurrentWord()
	return L.Push(word, pos+1)
}

func callFirstWord(L lua.Lua) int {
	this, stack_count := getBufferForCallBack(L)
	if this == nil {
		return stack_count
	}
	word := conio.QuotedFirstWord(this.String())
	return L.Push(word, 0)
}

func callBoxListing(L lua.Lua) int {
	// stack +1: readline.Buffer
	// stack +2: table
	// stack +3: index or value
	this, stack_count := getBufferForCallBack(L)
	if this == nil {
		return stack_count
	}
	fmt.Print("\n")
	list := make([]string, 0, 100)
	for i := 1; ; i++ {
		L.Push(i)     // to +3
		L.GetTable(2) //
		str, err := L.ToString(3)
		if err != nil {
			fmt.Fprintln(os.Stderr, "boxprint: "+err.Error())
			break
		}
		if str == "" {
			break
		}
		L.Pop(1)
		list = append(list, str)
	}
	conio.BoxPrint(list, os.Stdout)
	this.RepaintAll()
	return 0
}

func (this *KeyLuaFuncT) Call(buffer *conio.Buffer) conio.Result {
	this.L.LoadBufferX("", this.Chank, "b")
	pos := -1
	var text bytes.Buffer
	for i, c := range buffer.Buffer {
		if i >= buffer.Length {
			break
		}
		if i == buffer.Cursor {
			pos = text.Len() + 1
		}
		text.WriteRune(c)
	}
	if pos < 0 {
		pos = text.Len() + 1
	}

	this.L.Push(
		lua.TTable{
			Dict: map[string]lua.Pushable{
				"pos":       lua.Integer(pos),
				"text":      lua.TString{text.String()},
				"buffer":    lua.TLightUserData{unsafe.Pointer(buffer)},
				"call":      lua.TGoFunction{callKeyFunc},
				"insert":    lua.TGoFunction{callInsert},
				"lastword":  lua.TGoFunction{callLastWord},
				"firstword": lua.TGoFunction{callFirstWord},
				"boxprint":  lua.TGoFunction{callBoxListing},
			},
			Array: map[int]lua.Pushable{},
		})
	if err := this.L.Call(1, 1); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}
	switch this.L.GetType(-1) {
	case lua.LUA_TSTRING:
		str, strErr := this.L.ToString(-1)
		if strErr == nil {
			buffer.InsertAndRepaint(str)
		}
	case lua.LUA_TBOOLEAN:
		if !this.L.ToBool(-1) {
			buffer.Buffer = []rune{}
			buffer.Length = 0
		}
		return conio.ENTER
	}
	return conio.CONTINUE
}

func cmdBindKey(L lua.Lua) int {
	key, keyErr := L.ToString(-2)
	if keyErr != nil {
		return L.Push(keyErr)
	}
	key = strings.Replace(strings.ToUpper(key), "-", "_", -1)
	switch L.GetType(-1) {
	case lua.LUA_TFUNCTION:
		chank := L.Dump()
		if err := conio.BindKeyFunc(key, &KeyLuaFuncT{L, chank}); err != nil {
			return L.Push(nil, err)
		} else {
			return L.Push(true)
		}
	default:
		val, valErr := L.ToString(-1)
		if valErr != nil {
			return L.Push(nil, valErr)
		}
		err := conio.BindKeySymbol(key, val)
		if err != nil {
			return L.Push(nil, err)
		} else {
			return L.Push(true)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	_ "fmt"
	"github.com/nbari/violetear"
)

func main() {
	router := violetear.New()

	router.AddRegex(":uuid", `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`)
	router.AddRegex(":ip", `^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`)

	router.Add("/hello/world", "hello_world")
	router.Add("/hello/world/get", "hello_world", "GET")
	router.Add("/", "main")
	router.Add(":uuid", "h_uuid")
	router.Add("/ip/:ip", "h_uuid")

	//	log.Fatal("teste", router)
}
<commit_msg>	modified:   main.go<commit_after>package main

import (
	"fmt"
	"github.com/nbari/violetear"
	//	"log"
	"net/http"
)

func hello_world(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
	router := violetear.New(true)

	router.AddRegex(":uuid", `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`)

	router.AddPath("/hello/world", hello_world)
	router.AddPath(":uuid", hello_world)

	router.Run(":8080")

}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package acceptance_test

import (
	"fmt"
	"net/http"
	"os/exec"
	"strings"
	"testing"

	"github.com/GoogleCloudPlatform/buildpacks/internal/acceptance"
)

var (
	// goVersionsWithoutGCFSupport contains the list of go versions for which
	// there is GCP or GAE support, but not GCF.
	goVersionsWithoutGCFSupport = []string{"1.12", "1.14", "1.15"}
	// excludedGoVersions is the set of versions for which the regular
	// acceptance tests are not run.
	excludedGoVersions = make(map[string]string)
)

func init() {
	// The tests in this file are lengthy to run due to the number of
	// dependencies pulled in by FF and the test apps. In addition,
	// some of them do not pass for unsupported go versions. For that
	// reason exclude the versions without GCF support.
	for _, v := range goVersionsWithoutGCFSupport {
		excludedGoVersions[v] = v
	}
	// go111 has several differences from the new runtimes and many
	// of the test cases have subtle differences for that reaosn, it
	// is tested seperately.
	excludedGoVersions["1.11"] = "1.11"
}

func vendorSetup(builder, src string) error {
	// The setup function runs `go mod vendor` to vendor dependencies
	// specified in go.mod.
	args := strings.Fields(fmt.Sprintf("docker run --rm -v %s:/workspace -w /workspace -u root %s go mod vendor", src, builder))
	cmd := exec.Command(args[0], args[1:]...)
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("vendoring dependencies: %v, output:\n%s", err, out)
	}
	return nil
}

func goSumSetup(builder, src string) error {
	// The setup function runs `go mod vendor` to vendor dependencies
	// specified in go.mod.
	args := strings.Fields(fmt.Sprintf("docker run --rm -v %s:/workspace -w /workspace -u root %s go mod tidy", src, builder))
	cmd := exec.Command(args[0], args[1:]...)
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("generating go.sum: %v, output:\n%s", err, out)
	}
	return nil
}

func TestGCFAcceptanceGo(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.Test{
		{
			Name: "function without deps",
			App:  "no_deps",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name:       "vendored function without dependencies",
			App:        "no_framework_vendored",
			Env:        []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:       "/Func",
			MustOutput: []string{"Found function with vendored dependencies excluding functions-framework"},
		},
		{
			Name:       "function without framework",
			App:        "no_framework",
			Env:        []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:       "/Func",
			MustOutput: []string{"go.sum not found, generating"},
		},
		{
			Name:          "function with go.sum",
			App:           "no_framework",
			Env:           []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Setup:         goSumSetup,
			Path:          "/Func",
			MustNotOutput: []string{"go.sum not found, generating"},
		},
		{
			Name:  "vendored function with framework",
			App:   "with_framework",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "function with old framework",
			App:  "with_framework_old_version",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name:  "vendored function with old framework",
			App:   "with_framework_old_version",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "function at /*",
			App:  "no_framework",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/",
		},
		{
			Name: "function with subdirectories",
			App:  "with_subdir",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name: "declarative http function",
			App:  "declarative_http",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name: "declarative http anonymous function",
			App:  "declarative_anonymous",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name:        "declarative cloudevent function",
			App:         "declarative_cloud_event",
			RequestType: acceptance.CloudEventType,
			MustMatch:   "",
			Env:         []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name:        "non declarative cloudevent function",
			App:         "non_declarative_cloud_event",
			RequestType: acceptance.CloudEventType,
			MustMatch:   "",
			Env:         []string{"GOOGLE_FUNCTION_TARGET=Func", "GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent"},
		},
		{
			Name: "declarative and non declarative registration",
			App:  "declarative_old_and_new",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name:                "no auto registration in main.go if declarative detected",
			App:                 "declarative_cloud_event",
			RequestType:         acceptance.CloudEventType,
			MustMatchStatusCode: 404,
			MustMatch:           "404 page not found",
			// If the buildpack detects the declarative functions package, then
			// functions must be explicitly registered. The main.go written out
			// by the buildpack will NOT use the GOOGLE_FUNCTION_TARGET env var
			// to register a non-declarative function.
			Env: []string{"GOOGLE_FUNCTION_TARGET=NonDeclarativeFunc", "GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent"},
		},
		{
			Name:                "declarative function signature but wrong target",
			App:                 "declarative_http",
			Env:                 []string{"GOOGLE_FUNCTION_TARGET=ThisDoesntExist"},
			MustMatchStatusCode: 404,
			MustMatch:           "404 page not found",
		},
		{
			Name:        "background function",
			App:         "background_function",
			RequestType: acceptance.BackgroundEventType,
			Env:         []string{"GOOGLE_FUNCTION_TARGET=Func", "GOOGLE_FUNCTION_SIGNATURE_TYPE=event"},
		},
	}

	for _, tc := range testCases {
		tc.Env = append(tc.Env, "X_GOOGLE_TARGET_PLATFORM=gcf")
		tc.FilesMustExist = append(tc.FilesMustExist,
			"/layers/google.utils.archive-source/src/source-code.tar.gz",
			"/workspace/.googlebuild/source-code.tar.gz",
		)
		for _, v := range goVersions {
			if shouldSkipVersion(v) {
				continue
			}
			verTC := applyRuntimeVersionTest(t, tc, v)
			t.Run(verTC.Name, func(t *testing.T) {
				t.Parallel()
				if verTC.Setup != nil {
					t.Skip("TODO: The setup functions require go to be pre-installed which is not true for the unified builder")
				}
				acceptance.TestApp(t, builder, verTC)
			})
		}
	}
}

func shouldSkipVersion(version string) bool {
	_, ok := excludedGoVersions[version]
	return ok
}

func TestGCFFailuresGo(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.FailureTest{
		{
			App:       "no_framework_relative",
			Env:       []string{"GOOGLE_FUNCTION_TARGET=Func"},
			MustMatch: "the module path in the function's go.mod must contain a dot in the first path element before a slash, e.g. example.com/module, found: func",
		},
		{
			App:       "no_framework",
			Env:       []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Setup:     vendorSetup,
			MustMatch: "vendored dependencies must include \"github.com/GoogleCloudPlatform/functions-framework-go\"; if your function does not depend on the module, please add a blank import: `_ \"github.com/GoogleCloudPlatform/functions-framework-go/funcframework\"`",
		},
	}

	for _, tc := range testCases {
		tc.Env = append(tc.Env, "X_GOOGLE_TARGET_PLATFORM=gcf")
		for _, v := range goVersions {
			if shouldSkipVersion(v) {
				continue
			}
			verTC := applyRuntimeVersionFailureTest(t, tc, v)
			t.Run(verTC.Name, func(t *testing.T) {
				t.Parallel()
				if verTC.Setup != nil {
					t.Skip("TODO: The setup functions require go to be pre-installed which is not true for the unified builder")
				}
				acceptance.TestBuildFailure(t, builder, verTC)
			})
		}
	}
}

func TestGCFAcceptanceGo111(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.Test{
		{
			Name: "function without deps",
			App:  "no_deps",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name: "vendored function without dependencies",
			App:  "no_framework_vendored",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name: "function without framework",
			App:  "no_framework",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name: "function with go.sum",
			App:  "no_framework_go_sum",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name:  "vendored function without framework",
			App:   "no_framework",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "function with old framework",
			App:  "with_framework_old_version",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name:  "vendored function with old framework",
			App:   "with_framework_old_version",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "function at /*",
			App:  "no_framework",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/",
		},
		{
			Name: "function with subdirectories",
			App:  "with_subdir",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name:  "set GOPATH incorrectly",
			App:   "no_framework",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func", "GOPATH=/tmp"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "X_GOOGLE_ENTRY_POINT ignored",
			App:  "invalid_signature",
			// "Func" is the correct name of target function.
			// X_GOOGLE_ENTRY_POINT is irrelevant for function execution (only
			// used for logging an error message when there's an invalid signature).
			Env:                 []string{"GOOGLE_FUNCTION_TARGET=Func"},
			RunEnv:              []string{"X_GOOGLE_ENTRY_POINT=EntryPoint"},
			MustMatchStatusCode: http.StatusInternalServerError,
			MustMatch:           "func EntryPoint is of the type func(http.ResponseWriter, string), expected func(http.ResponseWriter, *http.Request)",
		},
		{
			Name: "X_GOOGLE_WORKER_PORT used over PORT",
			App:  "no_deps",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			// "8080" is the correct port to serve on.
			RunEnv: []string{"PORT=1234", "X_GOOGLE_WORKER_PORT=8080"},
		},
		{
			Name: "user module name without dot in path",
			App:  "no_framework_relative",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
	}

	for _, tc := range testCases {
		tc.Env = append(tc.Env, "X_GOOGLE_TARGET_PLATFORM=gcf")
		tc := applyRuntimeVersionTest(t, tc, "1.11")
		t.Run(tc.Name, func(t *testing.T) {
			t.Parallel()
			if tc.Setup != nil {
				t.Skip("TODO: The setup functions require go to be pre-installed which is not true for the unified builder")
			}
			tc.FilesMustExist = append(tc.FilesMustExist,
				"/layers/google.utils.archive-source/src/source-code.tar.gz",
				"/workspace/.googlebuild/source-code.tar.gz",
			)
			acceptance.TestApp(t, builder, tc)
		})
	}
}

func TestGCFFailuresGo111(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.FailureTest{
		{
			App: "with_framework",
			Env: []string{"GOOGLE_FUNCTION_TARGET=Func"},
			// Functions Framework v1.1.0+ supports CloudEvents functions,
			// which requires the cloudevents SDK v2.2.0 which requires Go 1.13+
			MustMatch: "module requires Go 1.13",
		},
	}

	for _, tc := range testCases {
		tc.Env = append(tc.Env, "X_GOOGLE_TARGET_PLATFORM=gcf")
		tc := applyRuntimeVersionFailureTest(t, tc, "1.11")
		t.Run(tc.App, func(t *testing.T) {
			t.Parallel()
			acceptance.TestBuildFailure(t, builder, tc)
		})
	}
}
<commit_msg>Fix a typo in comment<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package acceptance_test

import (
	"fmt"
	"net/http"
	"os/exec"
	"strings"
	"testing"

	"github.com/GoogleCloudPlatform/buildpacks/internal/acceptance"
)

var (
	// goVersionsWithoutGCFSupport contains the list of go versions for which
	// there is GCP or GAE support, but not GCF.
	goVersionsWithoutGCFSupport = []string{"1.12", "1.14", "1.15"}
	// excludedGoVersions is the set of versions for which the regular
	// acceptance tests are not run.
	excludedGoVersions = make(map[string]string)
)

func init() {
	// The tests in this file are lengthy to run due to the number of
	// dependencies pulled in by FF and the test apps. In addition,
	// some of them do not pass for unsupported go versions. For that
	// reason exclude the versions without GCF support.
	for _, v := range goVersionsWithoutGCFSupport {
		excludedGoVersions[v] = v
	}
	// go111 has several differences from the new runtimes and many
	// of the test cases have subtle differences for that reason, it
	// is tested seperately.
	excludedGoVersions["1.11"] = "1.11"
}

func vendorSetup(builder, src string) error {
	// The setup function runs `go mod vendor` to vendor dependencies
	// specified in go.mod.
	args := strings.Fields(fmt.Sprintf("docker run --rm -v %s:/workspace -w /workspace -u root %s go mod vendor", src, builder))
	cmd := exec.Command(args[0], args[1:]...)
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("vendoring dependencies: %v, output:\n%s", err, out)
	}
	return nil
}

func goSumSetup(builder, src string) error {
	// The setup function runs `go mod vendor` to vendor dependencies
	// specified in go.mod.
	args := strings.Fields(fmt.Sprintf("docker run --rm -v %s:/workspace -w /workspace -u root %s go mod tidy", src, builder))
	cmd := exec.Command(args[0], args[1:]...)
	if out, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("generating go.sum: %v, output:\n%s", err, out)
	}
	return nil
}

func TestGCFAcceptanceGo(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.Test{
		{
			Name: "function without deps",
			App:  "no_deps",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name:       "vendored function without dependencies",
			App:        "no_framework_vendored",
			Env:        []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:       "/Func",
			MustOutput: []string{"Found function with vendored dependencies excluding functions-framework"},
		},
		{
			Name:       "function without framework",
			App:        "no_framework",
			Env:        []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:       "/Func",
			MustOutput: []string{"go.sum not found, generating"},
		},
		{
			Name:          "function with go.sum",
			App:           "no_framework",
			Env:           []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Setup:         goSumSetup,
			Path:          "/Func",
			MustNotOutput: []string{"go.sum not found, generating"},
		},
		{
			Name:  "vendored function with framework",
			App:   "with_framework",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "function with old framework",
			App:  "with_framework_old_version",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name:  "vendored function with old framework",
			App:   "with_framework_old_version",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "function at /*",
			App:  "no_framework",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/",
		},
		{
			Name: "function with subdirectories",
			App:  "with_subdir",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name: "declarative http function",
			App:  "declarative_http",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name: "declarative http anonymous function",
			App:  "declarative_anonymous",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name:        "declarative cloudevent function",
			App:         "declarative_cloud_event",
			RequestType: acceptance.CloudEventType,
			MustMatch:   "",
			Env:         []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name:        "non declarative cloudevent function",
			App:         "non_declarative_cloud_event",
			RequestType: acceptance.CloudEventType,
			MustMatch:   "",
			Env:         []string{"GOOGLE_FUNCTION_TARGET=Func", "GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent"},
		},
		{
			Name: "declarative and non declarative registration",
			App:  "declarative_old_and_new",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name:                "no auto registration in main.go if declarative detected",
			App:                 "declarative_cloud_event",
			RequestType:         acceptance.CloudEventType,
			MustMatchStatusCode: 404,
			MustMatch:           "404 page not found",
			// If the buildpack detects the declarative functions package, then
			// functions must be explicitly registered. The main.go written out
			// by the buildpack will NOT use the GOOGLE_FUNCTION_TARGET env var
			// to register a non-declarative function.
			Env: []string{"GOOGLE_FUNCTION_TARGET=NonDeclarativeFunc", "GOOGLE_FUNCTION_SIGNATURE_TYPE=cloudevent"},
		},
		{
			Name:                "declarative function signature but wrong target",
			App:                 "declarative_http",
			Env:                 []string{"GOOGLE_FUNCTION_TARGET=ThisDoesntExist"},
			MustMatchStatusCode: 404,
			MustMatch:           "404 page not found",
		},
		{
			Name:        "background function",
			App:         "background_function",
			RequestType: acceptance.BackgroundEventType,
			Env:         []string{"GOOGLE_FUNCTION_TARGET=Func", "GOOGLE_FUNCTION_SIGNATURE_TYPE=event"},
		},
	}

	for _, tc := range testCases {
		tc.Env = append(tc.Env, "X_GOOGLE_TARGET_PLATFORM=gcf")
		tc.FilesMustExist = append(tc.FilesMustExist,
			"/layers/google.utils.archive-source/src/source-code.tar.gz",
			"/workspace/.googlebuild/source-code.tar.gz",
		)
		for _, v := range goVersions {
			if shouldSkipVersion(v) {
				continue
			}
			verTC := applyRuntimeVersionTest(t, tc, v)
			t.Run(verTC.Name, func(t *testing.T) {
				t.Parallel()
				if verTC.Setup != nil {
					t.Skip("TODO: The setup functions require go to be pre-installed which is not true for the unified builder")
				}
				acceptance.TestApp(t, builder, verTC)
			})
		}
	}
}

func shouldSkipVersion(version string) bool {
	_, ok := excludedGoVersions[version]
	return ok
}

func TestGCFFailuresGo(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.FailureTest{
		{
			App:       "no_framework_relative",
			Env:       []string{"GOOGLE_FUNCTION_TARGET=Func"},
			MustMatch: "the module path in the function's go.mod must contain a dot in the first path element before a slash, e.g. example.com/module, found: func",
		},
		{
			App:       "no_framework",
			Env:       []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Setup:     vendorSetup,
			MustMatch: "vendored dependencies must include \"github.com/GoogleCloudPlatform/functions-framework-go\"; if your function does not depend on the module, please add a blank import: `_ \"github.com/GoogleCloudPlatform/functions-framework-go/funcframework\"`",
		},
	}

	for _, tc := range testCases {
		tc.Env = append(tc.Env, "X_GOOGLE_TARGET_PLATFORM=gcf")
		for _, v := range goVersions {
			if shouldSkipVersion(v) {
				continue
			}
			verTC := applyRuntimeVersionFailureTest(t, tc, v)
			t.Run(verTC.Name, func(t *testing.T) {
				t.Parallel()
				if verTC.Setup != nil {
					t.Skip("TODO: The setup functions require go to be pre-installed which is not true for the unified builder")
				}
				acceptance.TestBuildFailure(t, builder, verTC)
			})
		}
	}
}

func TestGCFAcceptanceGo111(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.Test{
		{
			Name: "function without deps",
			App:  "no_deps",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name: "vendored function without dependencies",
			App:  "no_framework_vendored",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name: "function without framework",
			App:  "no_framework",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name: "function with go.sum",
			App:  "no_framework_go_sum",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name:  "vendored function without framework",
			App:   "no_framework",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "function with old framework",
			App:  "with_framework_old_version",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/Func",
		},
		{
			Name:  "vendored function with old framework",
			App:   "with_framework_old_version",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "function at /*",
			App:  "no_framework",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			Path: "/",
		},
		{
			Name: "function with subdirectories",
			App:  "with_subdir",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
		{
			Name:  "set GOPATH incorrectly",
			App:   "no_framework",
			Env:   []string{"GOOGLE_FUNCTION_TARGET=Func", "GOPATH=/tmp"},
			Path:  "/Func",
			Setup: vendorSetup,
		},
		{
			Name: "X_GOOGLE_ENTRY_POINT ignored",
			App:  "invalid_signature",
			// "Func" is the correct name of target function.
			// X_GOOGLE_ENTRY_POINT is irrelevant for function execution (only
			// used for logging an error message when there's an invalid signature).
			Env:                 []string{"GOOGLE_FUNCTION_TARGET=Func"},
			RunEnv:              []string{"X_GOOGLE_ENTRY_POINT=EntryPoint"},
			MustMatchStatusCode: http.StatusInternalServerError,
			MustMatch:           "func EntryPoint is of the type func(http.ResponseWriter, string), expected func(http.ResponseWriter, *http.Request)",
		},
		{
			Name: "X_GOOGLE_WORKER_PORT used over PORT",
			App:  "no_deps",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
			// "8080" is the correct port to serve on.
			RunEnv: []string{"PORT=1234", "X_GOOGLE_WORKER_PORT=8080"},
		},
		{
			Name: "user module name without dot in path",
			App:  "no_framework_relative",
			Env:  []string{"GOOGLE_FUNCTION_TARGET=Func"},
		},
	}

	for _, tc := range testCases {
		tc.Env = append(tc.Env, "X_GOOGLE_TARGET_PLATFORM=gcf")
		tc := applyRuntimeVersionTest(t, tc, "1.11")
		t.Run(tc.Name, func(t *testing.T) {
			t.Parallel()
			if tc.Setup != nil {
				t.Skip("TODO: The setup functions require go to be pre-installed which is not true for the unified builder")
			}
			tc.FilesMustExist = append(tc.FilesMustExist,
				"/layers/google.utils.archive-source/src/source-code.tar.gz",
				"/workspace/.googlebuild/source-code.tar.gz",
			)
			acceptance.TestApp(t, builder, tc)
		})
	}
}

func TestGCFFailuresGo111(t *testing.T) {
	builder, cleanup := acceptance.CreateBuilder(t)
	t.Cleanup(cleanup)

	testCases := []acceptance.FailureTest{
		{
			App: "with_framework",
			Env: []string{"GOOGLE_FUNCTION_TARGET=Func"},
			// Functions Framework v1.1.0+ supports CloudEvents functions,
			// which requires the cloudevents SDK v2.2.0 which requires Go 1.13+
			MustMatch: "module requires Go 1.13",
		},
	}

	for _, tc := range testCases {
		tc.Env = append(tc.Env, "X_GOOGLE_TARGET_PLATFORM=gcf")
		tc := applyRuntimeVersionFailureTest(t, tc, "1.11")
		t.Run(tc.App, func(t *testing.T) {
			t.Parallel()
			acceptance.TestBuildFailure(t, builder, tc)
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Knative Authors

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

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

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

package helpers

import (
	"fmt"
	"net/url"
	"sort"
	"strings"
	"testing"

	cloudevents "github.com/cloudevents/sdk-go/v2"
	"github.com/cloudevents/sdk-go/v2/binding/spec"
	cetest "github.com/cloudevents/sdk-go/v2/test"
	"github.com/google/uuid"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"knative.dev/eventing/pkg/apis/eventing/v1beta1"
	testlib "knative.dev/eventing/test/lib"
	"knative.dev/eventing/test/lib/recordevents"
	"knative.dev/eventing/test/lib/resources"
)

type eventTestCase struct {
	Type       string
	Source     string
	Extensions map[string]interface{}
}

// ToString converts the test case to a string to create names for different objects (e.g., triggers, services, etc.).
func (tc eventTestCase) String() string {
	eventType := tc.Type
	eventSource := tc.Source
	extensions := tc.Extensions
	// Pod names need to be lowercase. We might have an eventType as Any, that is why we lowercase them.
	if eventType == v1beta1.TriggerAnyFilter {
		eventType = "testany"
	}
	if eventSource == v1beta1.TriggerAnyFilter {
		eventSource = "testany"
	} else {
		u, _ := url.Parse(eventSource)
		eventSource = strings.Split(u.Host, ".")[0]
	}
	name := strings.ToLower(fmt.Sprintf("%s-%s", eventType, eventSource))
	if len(extensions) > 0 {
		name = strings.ToLower(fmt.Sprintf("%s-%s", name, extensionsToString(extensions)))
	}
	return name
}

// ToEventMatcher converts the test case to the event matcher
func (tc eventTestCase) ToEventMatcher() cetest.EventMatcher {
	var matchers []cetest.EventMatcher
	if tc.Type == v1beta1.TriggerAnyFilter {
		matchers = append(matchers, cetest.ContainsAttributes(spec.Type))
	} else {
		matchers = append(matchers, cetest.HasType(tc.Type))
	}

	if tc.Source == v1beta1.TriggerAnyFilter {
		matchers = append(matchers, cetest.ContainsAttributes(spec.Source))
	} else {
		matchers = append(matchers, cetest.HasSource(tc.Source))
	}

	for k, v := range tc.Extensions {
		if v == v1beta1.TriggerAnyFilter {
			matchers = append(matchers, cetest.ContainsExtensions(k))
		} else {
			matchers = append(matchers, cetest.HasExtension(k, v))
		}
	}

	return cetest.AllOf(matchers...)
}

// BrokerCreator creates a broker and returns its broker name.
// TestBrokerWithManyTriggers will wait for the broker to become ready.
type BrokerCreator func(client *testlib.Client) string

// ChannelBasedBrokerCreator creates a BrokerCreator that creates a broker based on the channel parameter.
func ChannelBasedBrokerCreator(channel metav1.TypeMeta, brokerClass string) BrokerCreator {
	return func(client *testlib.Client) string {
		brokerName := strings.ToLower(channel.Kind)

		// create a ConfigMap used by the broker.
		config := client.CreateBrokerConfigMapOrFail("config-"+brokerName, &channel)

		// create a new broker.
		client.CreateBrokerV1Beta1OrFail(brokerName,
			resources.WithBrokerClassForBrokerV1Beta1(brokerClass),
			resources.WithConfigForBrokerV1Beta1(config),
		)

		return brokerName
	}
}

// If shouldLabelNamespace is set to true this test annotates the testing namespace so that a default broker is created.
// It then binds many triggers with different filtering patterns to the broker created by brokerCreator, and sends
// different events to the broker's address.
// Finally, it verifies that only the appropriate events are routed to the subscribers.
func TestBrokerWithManyTriggers(t *testing.T, brokerCreator BrokerCreator, shouldLabelNamespace bool) {
	const (
		any          = v1beta1.TriggerAnyFilter
		eventType1   = "type1"
		eventType2   = "type2"
		eventSource1 = "http://source1.com"
		eventSource2 = "http://source2.com"
		// Be careful with the length of extension name and values,
		// we use extension name and value as a part of the name of resources like subscriber and trigger,
		// the maximum characters allowed of resource name is 63
		extensionName1            = "extname1"
		extensionValue1           = "extval1"
		extensionName2            = "extname2"
		extensionValue2           = "extvalue2"
		nonMatchingExtensionName  = "nonmatchingextname"
		nonMatchingExtensionValue = "nonmatchingextval"
	)
	tests := []struct {
		name string
		// These are the event context attributes and extension attributes that will be send.
		eventsToSend []eventTestCase
		// These are the event context attributes and extension attributes that triggers will listen to,
		// to set in the subscriber and services pod
		// The attributes in these test cases will be used as assertions on the receivers
		eventFilters []eventTestCase
		//TriggerFilter with DeprecatedSourceAndType or not
		deprecatedTriggerFilter bool
		// Use v1beta1 trigger
		v1beta1 bool
	}{
		{
			name: "test default broker with many deprecated triggers",
			eventsToSend: []eventTestCase{
				{Type: eventType1, Source: eventSource1},
				{Type: eventType1, Source: eventSource2},
				{Type: eventType2, Source: eventSource1},
				{Type: eventType2, Source: eventSource2},
			},
			eventFilters: []eventTestCase{
				{Type: any, Source: any},
				{Type: eventType1, Source: any},
				{Type: any, Source: eventSource1},
				{Type: eventType1, Source: eventSource1},
			},
			deprecatedTriggerFilter: true,
		}, {
			name: "test default broker with many attribute triggers",
			eventsToSend: []eventTestCase{
				{Type: eventType1, Source: eventSource1},
				{Type: eventType1, Source: eventSource2},
				{Type: eventType2, Source: eventSource1},
				{Type: eventType2, Source: eventSource2},
			},
			eventFilters: []eventTestCase{
				{Type: any, Source: any},
				{Type: eventType1, Source: any},
				{Type: any, Source: eventSource1},
				{Type: eventType1, Source: eventSource1},
			},
			deprecatedTriggerFilter: false,
		}, {
			name: "test default broker with many attribute triggers using v1beta1 trigger",
			eventsToSend: []eventTestCase{
				{Type: eventType1, Source: eventSource1},
				{Type: eventType1, Source: eventSource2},
				{Type: eventType2, Source: eventSource1},
				{Type: eventType2, Source: eventSource2},
			},
			eventFilters: []eventTestCase{
				{Type: any, Source: any},
				{Type: eventType1, Source: any},
				{Type: any, Source: eventSource1},
				{Type: eventType1, Source: eventSource1},
			},
			deprecatedTriggerFilter: false,
			v1beta1:                 true,
		}, {
			name: "test default broker with many attribute and extension triggers",
			eventsToSend: []eventTestCase{
				{Type: eventType1, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: eventType1, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}},
				{Type: eventType1, Source: eventSource1, Extensions: map[string]interface{}{extensionName2: extensionValue2}},
				{Type: eventType1, Source: eventSource2, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: eventType2, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: nonMatchingExtensionValue}},
				{Type: eventType2, Source: eventSource2, Extensions: map[string]interface{}{nonMatchingExtensionName: extensionValue1}},
				{Type: eventType2, Source: eventSource2, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}},
				{Type: eventType2, Source: eventSource2, Extensions: map[string]interface{}{extensionName1: extensionValue1, nonMatchingExtensionName: extensionValue2}},
			},
			eventFilters: []eventTestCase{
				{Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}},
				{Type: any, Source: any, Extensions: map[string]interface{}{extensionName2: extensionValue2}},
				{Type: eventType1, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: any}},
				{Type: any, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: any, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}},
			},
			deprecatedTriggerFilter: false,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			client := testlib.Setup(t, true)
			defer testlib.TearDown(client)

			if shouldLabelNamespace {
				// Label namespace so that it creates the default broker.
				if err := client.LabelNamespace(map[string]string{"knative-eventing-injection": "enabled"}); err != nil {
					t.Fatalf("Error annotating namespace: %v", err)
				}
			}

			brokerName := brokerCreator(client)

			// Wait for broker ready.
			client.WaitForResourceReadyOrFail(brokerName, testlib.BrokerTypeMeta)

			if shouldLabelNamespace {
				// Test if namespace reconciler would recreate broker once broker was deleted.
				if err := client.Eventing.EventingV1beta1().Brokers(client.Namespace).Delete(brokerName, &metav1.DeleteOptions{}); err != nil {
					t.Fatalf("Can't delete default broker in namespace: %v", client.Namespace)
				}
				client.WaitForResourceReadyOrFail(brokerName, testlib.BrokerTypeMeta)
			}

			// Let's start event recorders and triggers
			eventTrackers := make(map[string]*recordevents.EventInfoStore, len(test.eventFilters))
			for _, event := range test.eventFilters {
				// Create event recorder pod and service
				subscriberName := "dumper-" + event.String()
				eventRecordPod := resources.EventRecordPod(subscriberName)
				client.CreatePodOrFail(eventRecordPod, testlib.WithService(subscriberName))
				eventTracker, err := recordevents.NewEventInfoStore(client, subscriberName)
				if err != nil {
					t.Fatalf("Pod tracker failed: %v", err)
				}
				eventTrackers[subscriberName] = eventTracker
				defer eventTracker.Cleanup()

				// Create trigger.
				triggerName := "trigger-" + event.String()
				client.CreateTriggerOrFailV1Beta1(triggerName,
					resources.WithSubscriberServiceRefForTriggerV1Beta1(subscriberName),
					resources.WithAttributesTriggerFilterV1Beta1(event.Source, event.Type, event.Extensions),
					resources.WithBrokerV1Beta1(brokerName),
				)
			}
			// Wait for all test resources to become ready before sending the events.
			client.WaitForAllTestResourcesReadyOrFail()

			// Map to save the expected matchers per dumper so that we can verify the delivery.
			expectedMatchers := make(map[string][]recordevents.EventInfoMatcher)
			// Map to save the unexpected matchers per dumper so that we can verify that they weren't delivered.
			unexpectedMatchers := make(map[string][]recordevents.EventInfoMatcher)

			// Now we need to send events and populate the expectedMatcher/unexpectedMatchers map,
			// in order to assert if I correctly receive only the expected events
			for _, eventTestCase := range test.eventsToSend {
				// Create cloud event.
				// Using event type, source and extensions as part of the body for easier debugging.
				eventToSend := cloudevents.NewEvent()
				eventToSend.SetID(uuid.New().String())
				eventToSend.SetType(eventTestCase.Type)
				eventToSend.SetSource(eventTestCase.Source)
				for k, v := range eventTestCase.Extensions {
					eventToSend.SetExtension(k, v)
				}

				data := fmt.Sprintf(`{"msg":"%s"}`, eventTestCase.String())
				if err := eventToSend.SetData(cloudevents.ApplicationJSON, []byte(data)); err != nil {
					t.Fatalf("Cannot set the payload of the event: %s", err.Error())
				}

				// Send event
				senderPodName := "sender-" + eventTestCase.String()
				client.SendEventToAddressable(senderPodName, brokerName, testlib.BrokerTypeMeta, eventToSend)

				// Sent event matcher
				sentEventMatcher := cetest.AllOf(
					cetest.HasId(eventToSend.ID()),
					eventTestCase.ToEventMatcher(),
				)

				// Check on every dumper whether we should expect this event or not
				for _, eventFilter := range test.eventFilters {
					subscriberName := "dumper-" + eventFilter.String()

					if eventFilter.ToEventMatcher()(eventToSend) == nil {
						// This filter should match this event
						expectedMatchers[subscriberName] = append(
							expectedMatchers[subscriberName],
							recordevents.MatchEvent(sentEventMatcher),
						)
					} else {
						// This filter should not match this event
						unexpectedMatchers[subscriberName] = append(
							unexpectedMatchers[subscriberName],
							recordevents.MatchEvent(sentEventMatcher),
						)
					}
				}
			}

			// Let's check that all expected matchers are fulfilled
			for subscriberName, matchers := range expectedMatchers {
				eventTracker := eventTrackers[subscriberName]

				for _, matcher := range matchers {
					// One match per event is enough
					eventTracker.AssertAtLeast(1, matcher)
				}
			}

			// Let's check the unexpected matchers
			// NOTE: this check is not really robust because we could receive
			// an unexpected event after the check is done
			for subscriberName, matchers := range unexpectedMatchers {
				eventTracker := eventTrackers[subscriberName]

				for _, matcher := range matchers {
					eventTracker.AssertNot(matcher)
				}
			}
		})
	}
}

func extensionsToString(extensions map[string]interface{}) string {
	// Sort extension keys
	sortedExtensionNames := make([]string, 0)
	for k := range extensions {
		sortedExtensionNames = append(sortedExtensionNames, k)
	}
	sort.Strings(sortedExtensionNames)

	// Write map as string
	var sb strings.Builder
	for _, sortedExtensionName := range sortedExtensionNames {
		sb.WriteString("-")
		sb.WriteString(sortedExtensionName)
		sb.WriteString("-")
		vStr := fmt.Sprintf("%v", extensions[sortedExtensionName])
		if vStr == v1beta1.TriggerAnyFilter {
			vStr = "testany"
		}
		sb.WriteString(vStr)
	}
	return sb.String()
}
<commit_msg>Wanna fix flakyness (#3423)<commit_after>/*
Copyright 2020 The Knative Authors

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

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

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

package helpers

import (
	"fmt"
	"net/url"
	"sort"
	"strings"
	"testing"

	cloudevents "github.com/cloudevents/sdk-go/v2"
	"github.com/cloudevents/sdk-go/v2/binding/spec"
	cetest "github.com/cloudevents/sdk-go/v2/test"
	"github.com/google/uuid"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"knative.dev/eventing/pkg/apis/eventing/v1beta1"
	testlib "knative.dev/eventing/test/lib"
	"knative.dev/eventing/test/lib/recordevents"
	"knative.dev/eventing/test/lib/resources"
)

type eventTestCase struct {
	Type       string
	Source     string
	Extensions map[string]interface{}
}

// ToString converts the test case to a string to create names for different objects (e.g., triggers, services, etc.).
func (tc eventTestCase) String() string {
	eventType := tc.Type
	eventSource := tc.Source
	extensions := tc.Extensions
	// Pod names need to be lowercase. We might have an eventType as Any, that is why we lowercase them.
	if eventType == v1beta1.TriggerAnyFilter {
		eventType = "testany"
	}
	if eventSource == v1beta1.TriggerAnyFilter {
		eventSource = "testany"
	} else {
		u, _ := url.Parse(eventSource)
		eventSource = strings.Split(u.Host, ".")[0]
	}
	name := strings.ToLower(fmt.Sprintf("%s-%s", eventType, eventSource))
	if len(extensions) > 0 {
		name = strings.ToLower(fmt.Sprintf("%s-%s", name, extensionsToString(extensions)))
	}
	return name
}

// ToEventMatcher converts the test case to the event matcher
func (tc eventTestCase) ToEventMatcher() cetest.EventMatcher {
	var matchers []cetest.EventMatcher
	if tc.Type == v1beta1.TriggerAnyFilter {
		matchers = append(matchers, cetest.ContainsAttributes(spec.Type))
	} else {
		matchers = append(matchers, cetest.HasType(tc.Type))
	}

	if tc.Source == v1beta1.TriggerAnyFilter {
		matchers = append(matchers, cetest.ContainsAttributes(spec.Source))
	} else {
		matchers = append(matchers, cetest.HasSource(tc.Source))
	}

	for k, v := range tc.Extensions {
		if v == v1beta1.TriggerAnyFilter {
			matchers = append(matchers, cetest.ContainsExtensions(k))
		} else {
			matchers = append(matchers, cetest.HasExtension(k, v))
		}
	}

	return cetest.AllOf(matchers...)
}

// BrokerCreator creates a broker and returns its broker name.
// TestBrokerWithManyTriggers will wait for the broker to become ready.
type BrokerCreator func(client *testlib.Client) string

// ChannelBasedBrokerCreator creates a BrokerCreator that creates a broker based on the channel parameter.
func ChannelBasedBrokerCreator(channel metav1.TypeMeta, brokerClass string) BrokerCreator {
	return func(client *testlib.Client) string {
		brokerName := strings.ToLower(channel.Kind)

		// create a ConfigMap used by the broker.
		config := client.CreateBrokerConfigMapOrFail("config-"+brokerName, &channel)

		// create a new broker.
		client.CreateBrokerV1Beta1OrFail(brokerName,
			resources.WithBrokerClassForBrokerV1Beta1(brokerClass),
			resources.WithConfigForBrokerV1Beta1(config),
		)

		return brokerName
	}
}

// If shouldLabelNamespace is set to true this test annotates the testing namespace so that a default broker is created.
// It then binds many triggers with different filtering patterns to the broker created by brokerCreator, and sends
// different events to the broker's address.
// Finally, it verifies that only the appropriate events are routed to the subscribers.
func TestBrokerWithManyTriggers(t *testing.T, brokerCreator BrokerCreator, shouldLabelNamespace bool) {
	const (
		any          = v1beta1.TriggerAnyFilter
		eventType1   = "type1"
		eventType2   = "type2"
		eventSource1 = "http://source1.com"
		eventSource2 = "http://source2.com"
		// Be careful with the length of extension name and values,
		// we use extension name and value as a part of the name of resources like subscriber and trigger,
		// the maximum characters allowed of resource name is 63
		extensionName1            = "extname1"
		extensionValue1           = "extval1"
		extensionName2            = "extname2"
		extensionValue2           = "extvalue2"
		nonMatchingExtensionName  = "nonmatchingextname"
		nonMatchingExtensionValue = "nonmatchingextval"
	)
	tests := []struct {
		name string
		// These are the event context attributes and extension attributes that will be send.
		eventsToSend []eventTestCase
		// These are the event context attributes and extension attributes that triggers will listen to,
		// to set in the subscriber and services pod
		// The attributes in these test cases will be used as assertions on the receivers
		eventFilters []eventTestCase
		//TriggerFilter with DeprecatedSourceAndType or not
		deprecatedTriggerFilter bool
		// Use v1beta1 trigger
		v1beta1 bool
	}{
		{
			name: "test default broker with many deprecated triggers",
			eventsToSend: []eventTestCase{
				{Type: eventType1, Source: eventSource1},
				{Type: eventType1, Source: eventSource2},
				{Type: eventType2, Source: eventSource1},
				{Type: eventType2, Source: eventSource2},
			},
			eventFilters: []eventTestCase{
				{Type: any, Source: any},
				{Type: eventType1, Source: any},
				{Type: any, Source: eventSource1},
				{Type: eventType1, Source: eventSource1},
			},
			deprecatedTriggerFilter: true,
		}, {
			name: "test default broker with many attribute triggers",
			eventsToSend: []eventTestCase{
				{Type: eventType1, Source: eventSource1},
				{Type: eventType1, Source: eventSource2},
				{Type: eventType2, Source: eventSource1},
				{Type: eventType2, Source: eventSource2},
			},
			eventFilters: []eventTestCase{
				{Type: any, Source: any},
				{Type: eventType1, Source: any},
				{Type: any, Source: eventSource1},
				{Type: eventType1, Source: eventSource1},
			},
			deprecatedTriggerFilter: false,
		}, {
			name: "test default broker with many attribute triggers using v1beta1 trigger",
			eventsToSend: []eventTestCase{
				{Type: eventType1, Source: eventSource1},
				{Type: eventType1, Source: eventSource2},
				{Type: eventType2, Source: eventSource1},
				{Type: eventType2, Source: eventSource2},
			},
			eventFilters: []eventTestCase{
				{Type: any, Source: any},
				{Type: eventType1, Source: any},
				{Type: any, Source: eventSource1},
				{Type: eventType1, Source: eventSource1},
			},
			deprecatedTriggerFilter: false,
			v1beta1:                 true,
		}, {
			name: "test default broker with many attribute and extension triggers",
			eventsToSend: []eventTestCase{
				{Type: eventType1, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: eventType1, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}},
				{Type: eventType1, Source: eventSource1, Extensions: map[string]interface{}{extensionName2: extensionValue2}},
				{Type: eventType1, Source: eventSource2, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: eventType2, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: nonMatchingExtensionValue}},
				{Type: eventType2, Source: eventSource2, Extensions: map[string]interface{}{nonMatchingExtensionName: extensionValue1}},
				{Type: eventType2, Source: eventSource2, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}},
				{Type: eventType2, Source: eventSource2, Extensions: map[string]interface{}{extensionName1: extensionValue1, nonMatchingExtensionName: extensionValue2}},
			},
			eventFilters: []eventTestCase{
				{Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}},
				{Type: any, Source: any, Extensions: map[string]interface{}{extensionName2: extensionValue2}},
				{Type: eventType1, Source: any, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: any, Source: any, Extensions: map[string]interface{}{extensionName1: any}},
				{Type: any, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1}},
				{Type: any, Source: eventSource1, Extensions: map[string]interface{}{extensionName1: extensionValue1, extensionName2: extensionValue2}},
			},
			deprecatedTriggerFilter: false,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			client := testlib.Setup(t, true)
			defer testlib.TearDown(client)

			if shouldLabelNamespace {
				// Label namespace so that it creates the default broker.
				if err := client.LabelNamespace(map[string]string{"knative-eventing-injection": "enabled"}); err != nil {
					t.Fatalf("Error annotating namespace: %v", err)
				}
			}

			brokerName := brokerCreator(client)

			// Wait for broker ready.
			client.WaitForResourceReadyOrFail(brokerName, testlib.BrokerTypeMeta)

			if shouldLabelNamespace {
				// Test if namespace reconciler would recreate broker once broker was deleted.
				if err := client.Eventing.EventingV1beta1().Brokers(client.Namespace).Delete(brokerName, &metav1.DeleteOptions{}); err != nil {
					t.Fatalf("Can't delete default broker in namespace: %v", client.Namespace)
				}
				client.WaitForResourceReadyOrFail(brokerName, testlib.BrokerTypeMeta)
			}

			// Let's start event recorders and triggers
			eventTrackers := make(map[string]*recordevents.EventInfoStore, len(test.eventFilters))
			for _, event := range test.eventFilters {
				// Create event recorder pod and service
				subscriberName := "dumper-" + event.String()
				eventTracker, _ := recordevents.StartEventRecordOrFail(client, subscriberName)
				eventTrackers[subscriberName] = eventTracker
				defer eventTracker.Cleanup()

				// Create trigger.
				triggerName := "trigger-" + event.String()
				client.CreateTriggerOrFailV1Beta1(triggerName,
					resources.WithSubscriberServiceRefForTriggerV1Beta1(subscriberName),
					resources.WithAttributesTriggerFilterV1Beta1(event.Source, event.Type, event.Extensions),
					resources.WithBrokerV1Beta1(brokerName),
				)
			}
			// Wait for all test resources to become ready before sending the events.
			client.WaitForAllTestResourcesReadyOrFail()

			// Map to save the expected matchers per dumper so that we can verify the delivery.
			expectedMatchers := make(map[string][]recordevents.EventInfoMatcher)
			// Map to save the unexpected matchers per dumper so that we can verify that they weren't delivered.
			unexpectedMatchers := make(map[string][]recordevents.EventInfoMatcher)

			// Now we need to send events and populate the expectedMatcher/unexpectedMatchers map,
			// in order to assert if I correctly receive only the expected events
			for _, eventTestCase := range test.eventsToSend {
				// Create cloud event.
				// Using event type, source and extensions as part of the body for easier debugging.
				eventToSend := cloudevents.NewEvent()
				eventToSend.SetID(uuid.New().String())
				eventToSend.SetType(eventTestCase.Type)
				eventToSend.SetSource(eventTestCase.Source)
				for k, v := range eventTestCase.Extensions {
					eventToSend.SetExtension(k, v)
				}

				data := fmt.Sprintf(`{"msg":"%s"}`, eventTestCase.String())
				if err := eventToSend.SetData(cloudevents.ApplicationJSON, []byte(data)); err != nil {
					t.Fatalf("Cannot set the payload of the event: %s", err.Error())
				}

				// Send event
				senderPodName := "sender-" + eventTestCase.String()
				client.SendEventToAddressable(senderPodName, brokerName, testlib.BrokerTypeMeta, eventToSend)

				// Sent event matcher
				sentEventMatcher := cetest.AllOf(
					cetest.HasId(eventToSend.ID()),
					eventTestCase.ToEventMatcher(),
				)

				// Check on every dumper whether we should expect this event or not
				for _, eventFilter := range test.eventFilters {
					subscriberName := "dumper-" + eventFilter.String()

					if eventFilter.ToEventMatcher()(eventToSend) == nil {
						// This filter should match this event
						expectedMatchers[subscriberName] = append(
							expectedMatchers[subscriberName],
							recordevents.MatchEvent(sentEventMatcher),
						)
					} else {
						// This filter should not match this event
						unexpectedMatchers[subscriberName] = append(
							unexpectedMatchers[subscriberName],
							recordevents.MatchEvent(sentEventMatcher),
						)
					}
				}
			}

			// Let's check that all expected matchers are fulfilled
			for subscriberName, matchers := range expectedMatchers {
				eventTracker := eventTrackers[subscriberName]

				for _, matcher := range matchers {
					// One match per event is enough
					eventTracker.AssertAtLeast(1, matcher)
				}
			}

			// Let's check the unexpected matchers
			// NOTE: this check is not really robust because we could receive
			// an unexpected event after the check is done
			for subscriberName, matchers := range unexpectedMatchers {
				eventTracker := eventTrackers[subscriberName]

				for _, matcher := range matchers {
					eventTracker.AssertNot(matcher)
				}
			}
		})
	}
}

func extensionsToString(extensions map[string]interface{}) string {
	// Sort extension keys
	sortedExtensionNames := make([]string, 0)
	for k := range extensions {
		sortedExtensionNames = append(sortedExtensionNames, k)
	}
	sort.Strings(sortedExtensionNames)

	// Write map as string
	var sb strings.Builder
	for _, sortedExtensionName := range sortedExtensionNames {
		sb.WriteString("-")
		sb.WriteString(sortedExtensionName)
		sb.WriteString("-")
		vStr := fmt.Sprintf("%v", extensions[sortedExtensionName])
		if vStr == v1beta1.TriggerAnyFilter {
			vStr = "testany"
		}
		sb.WriteString(vStr)
	}
	return sb.String()
}
<|endoftext|>


<commit_before>package test_util

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

// CheckStringsMatch adds an error to t iff the elements in got do
// not match exactly the elements in expected (irrespective of order).
func CheckStringsMatch(t *testing.T, expected []string, got []string) {
	gotMap, expectedMap := map[string]struct{}{}, map[string]struct{}{}
	for _, path := range got {
		gotMap[path] = struct{}{}
	}
	for _, path := range expected {
		expectedMap[path] = struct{}{}
	}
	if !reflect.DeepEqual(gotMap, expectedMap) {
		t.Errorf("Paths were not correct.\nExpected: %v\nGot: %v\n", expected, got)
	}
}

// CheckFilesMatch adds an error to t iff the contents of the file at gotPath
// do not match exactly the contents of the file at expectedPath.
func CheckFilesMatch(t *testing.T, expectedPath string, gotPath string) {
	expected, err := ioutil.ReadFile(expectedPath)
	if err != nil {
		t.Fatal(err)
	}
	if got, err := ioutil.ReadFile(gotPath); err != nil {
		if os.IsNotExist(err) {
			t.Errorf("File at %s did not exist.", gotPath)
		} else {
			t.Fatal(err)
		}
	} else if len(got) == 0 {
		t.Errorf("File at %s was empty.", gotPath)
	} else if !reflect.DeepEqual(expected, got) {
		t.Errorf("Contents of file at %s were incorrect.\nExpected: %s\nGot: %s\n", gotPath, string(expected), string(got))
	}
}

// CheckDirsMatch recursively iterates through expectedDir and checks that the directory
// structure and the contents of each file match gotDir exactly. If anything does not
// match, it adds an error to t.
func CheckDirsMatch(t *testing.T, expectedDir string, gotDir string) {
	if err := filepath.Walk(expectedDir, func(expectedPath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && !(expectedPath == expectedDir) {
			// We expect the directory structure to be the same, so every subdirectory
			// following expectedDir should also be present in gotDir. i.e. if expectedDir is
			// /tmp/source, gotDir is /tmp/public, and expectedPath is
			// /tmp/source/one/two/three.txt, we would expect the corresponding gotPath to
			// be /tmp/public/one/two/three.txt.
			gotPath := strings.Replace(expectedPath, expectedDir, gotDir, 1)
			CheckFilesMatch(t, expectedPath, gotPath)
		}
		return nil
	}); err != nil {
		panic(err)
	}
}
<commit_msg>Remove old "path" terminology from test_util.CheckStringsMatch<commit_after>package test_util

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

// CheckStringsMatch adds an error to t iff the elements in got do
// not match exactly the elements in expected (irrespective of order).
func CheckStringsMatch(t *testing.T, expected []string, got []string) {
	gotMap, expectedMap := map[string]struct{}{}, map[string]struct{}{}
	for _, s := range got {
		gotMap[s] = struct{}{}
	}
	for _, s := range expected {
		expectedMap[s] = struct{}{}
	}
	if !reflect.DeepEqual(gotMap, expectedMap) {
		t.Errorf("Paths were not correct.\nExpected: %v\nGot: %v\n", expected, got)
	}
}

// CheckFilesMatch adds an error to t iff the contents of the file at gotPath
// do not match exactly the contents of the file at expectedPath.
func CheckFilesMatch(t *testing.T, expectedPath string, gotPath string) {
	expected, err := ioutil.ReadFile(expectedPath)
	if err != nil {
		t.Fatal(err)
	}
	if got, err := ioutil.ReadFile(gotPath); err != nil {
		if os.IsNotExist(err) {
			t.Errorf("File at %s did not exist.", gotPath)
		} else {
			t.Fatal(err)
		}
	} else if len(got) == 0 {
		t.Errorf("File at %s was empty.", gotPath)
	} else if !reflect.DeepEqual(expected, got) {
		t.Errorf("Contents of file at %s were incorrect.\nExpected: %s\nGot: %s\n", gotPath, string(expected), string(got))
	}
}

// CheckDirsMatch recursively iterates through expectedDir and checks that the directory
// structure and the contents of each file match gotDir exactly. If anything does not
// match, it adds an error to t.
func CheckDirsMatch(t *testing.T, expectedDir string, gotDir string) {
	if err := filepath.Walk(expectedDir, func(expectedPath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && !(expectedPath == expectedDir) {
			// We expect the directory structure to be the same, so every subdirectory
			// following expectedDir should also be present in gotDir. i.e. if expectedDir is
			// /tmp/source, gotDir is /tmp/public, and expectedPath is
			// /tmp/source/one/two/three.txt, we would expect the corresponding gotPath to
			// be /tmp/public/one/two/three.txt.
			gotPath := strings.Replace(expectedPath, expectedDir, gotDir, 1)
			CheckFilesMatch(t, expectedPath, gotPath)
		}
		return nil
	}); err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package blackbox

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

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

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

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

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

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

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

	os.Exit(m.Run())
}

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

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

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

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

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

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

	oemLookasideDir := filepath.Join(tmpDirectory, "oem-lookaside")
	systemConfigDir := filepath.Join(tmpDirectory, "system")
	var rootPartition *types.Partition

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

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

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

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

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

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

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

		err = createFilesForPartitions(ctx, disk.Partitions)
		if err != nil {
			return err
		}

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

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

		if rootPartition == nil {
			rootPartition = getRootPartition(disk.Partitions)
		}
	}
	if rootPartition == nil {
		return fmt.Errorf("ROOT filesystem not found! A partition labeled ROOT is requred")
	}

	if strings.Contains(test.Config, "passwd") {
		if err := prepareRootPartitionForPasswd(ctx, rootPartition); err != nil {
			return err
		}
	}

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

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

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

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

	// Ignition
	appendEnv := []string{
		"IGNITION_OEM_DEVICE=" + test.In[0].Partitions.GetPartition("OEM").Device,
		"IGNITION_OEM_LOOKASIDE_DIR=" + oemLookasideDir,
		"IGNITION_SYSTEM_CONFIG_DIR=" + systemConfigDir,
	}
	disksErr := runIgnition(t, ctx, "disks", rootPartition.MountPath, tmpDirectory, appendEnv)
	if !negativeTests && disksErr != nil {
		return disksErr
	}

	var filesErr error
	if disksErr == nil {
		// Even if we're running negative tests, we shouldn't run the files stage if the disks stage
		// failed. This is how Ignition was designed to be used.
		if err := mountPartition(ctx, rootPartition); err != nil {
			return err
		}
		filesErr = runIgnition(t, ctx, "files", rootPartition.MountPath, tmpDirectory, appendEnv)
		if err := umountPartition(rootPartition); err != nil {
			return err
		}
	}

	if !negativeTests && filesErr != nil {
		return filesErr
	}
	if negativeTests && disksErr == nil && filesErr == nil {
		return fmt.Errorf("Expected failure and ignition succeeded")
	}

	for _, disk := range test.Out {
		if !negativeTests {
			err = validateDisk(t, disk)
			if err != nil {
				return err
			}
			err = validateFilesystems(t, disk.Partitions)
			if err != nil {
				return err
			}
			validateFilesDirectoriesAndLinks(t, ctx, disk.Partitions)
		}
	}
	return nil
}
<commit_msg>tests: add -list flag to blackbox tests<commit_after>// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package blackbox

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

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

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

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

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

	// flag for listing all subtests that would be run without running them
	listSubtests = false
)

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

	flag.BoolVar(&listSubtests, "list", false, "list tests that would be run without running them")
	flag.Parse()

	if !listSubtests {
		httpServer := &HTTPServer{}
		httpServer.Start()
		tftpServer := &TFTPServer{}
		tftpServer.Start()
	}
	os.Exit(m.Run())
}

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

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

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

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

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

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

	oemLookasideDir := filepath.Join(tmpDirectory, "oem-lookaside")
	systemConfigDir := filepath.Join(tmpDirectory, "system")
	var rootPartition *types.Partition

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

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

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

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

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

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

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

		err = createFilesForPartitions(ctx, disk.Partitions)
		if err != nil {
			return err
		}

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

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

		if rootPartition == nil {
			rootPartition = getRootPartition(disk.Partitions)
		}
	}
	if rootPartition == nil {
		return fmt.Errorf("ROOT filesystem not found! A partition labeled ROOT is requred")
	}

	if strings.Contains(test.Config, "passwd") {
		if err := prepareRootPartitionForPasswd(ctx, rootPartition); err != nil {
			return err
		}
	}

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

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

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

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

	// Ignition
	appendEnv := []string{
		"IGNITION_OEM_DEVICE=" + test.In[0].Partitions.GetPartition("OEM").Device,
		"IGNITION_OEM_LOOKASIDE_DIR=" + oemLookasideDir,
		"IGNITION_SYSTEM_CONFIG_DIR=" + systemConfigDir,
	}
	disksErr := runIgnition(t, ctx, "disks", rootPartition.MountPath, tmpDirectory, appendEnv)
	if !negativeTests && disksErr != nil {
		return disksErr
	}

	var filesErr error
	if disksErr == nil {
		// Even if we're running negative tests, we shouldn't run the files stage if the disks stage
		// failed. This is how Ignition was designed to be used.
		if err := mountPartition(ctx, rootPartition); err != nil {
			return err
		}
		filesErr = runIgnition(t, ctx, "files", rootPartition.MountPath, tmpDirectory, appendEnv)
		if err := umountPartition(rootPartition); err != nil {
			return err
		}
	}

	if !negativeTests && filesErr != nil {
		return filesErr
	}
	if negativeTests && disksErr == nil && filesErr == nil {
		return fmt.Errorf("Expected failure and ignition succeeded")
	}

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