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

// Package spdy is an incomplete implementation of the SPDY protocol.
//
// The implementation follows draft 2 of the spec:
// https://sites.google.com/a/chromium.org/dev/spdy/spdy-protocol/spdy-protocol-draft2
package spdy

import (
	"bytes"
	"compress/zlib"
	"encoding/binary"
	"http"
	"io"
	"os"
	"strconv"
	"strings"
	"sync"
)

// Version is the protocol version number that this package implements.
const Version = 2

// ControlFrameType stores the type field in a control frame header.
type ControlFrameType uint16

// Control frame type constants
const (
	TypeSynStream    ControlFrameType = 0x0001
	TypeSynReply     = 0x0002
	TypeRstStream    = 0x0003
	TypeSettings     = 0x0004
	TypeNoop         = 0x0005
	TypePing         = 0x0006
	TypeGoaway       = 0x0007
	TypeHeaders      = 0x0008
	TypeWindowUpdate = 0x0009
)

func (t ControlFrameType) String() string {
	switch t {
	case TypeSynStream:
		return "SYN_STREAM"
	case TypeSynReply:
		return "SYN_REPLY"
	case TypeRstStream:
		return "RST_STREAM"
	case TypeSettings:
		return "SETTINGS"
	case TypeNoop:
		return "NOOP"
	case TypePing:
		return "PING"
	case TypeGoaway:
		return "GOAWAY"
	case TypeHeaders:
		return "HEADERS"
	case TypeWindowUpdate:
		return "WINDOW_UPDATE"
	}
	return "Type(" + strconv.Itoa(int(t)) + ")"
}

type FrameFlags uint8

// Stream frame flags
const (
	FlagFin            FrameFlags = 0x01
	FlagUnidirectional = 0x02
)

// SETTINGS frame flags
const (
	FlagClearPreviouslyPersistedSettings = 0x01
)

// MaxDataLength is the maximum number of bytes that can be stored in one frame.
const MaxDataLength = 1<<24 - 1

// A Frame is a framed message as sent between clients and servers.
// There are two types of frames: control frames and data frames.
type Frame struct {
	Header [4]byte
	Flags  FrameFlags
	Data   []byte
}

// ControlFrame creates a control frame with the given information.
func ControlFrame(t ControlFrameType, f FrameFlags, data []byte) Frame {
	return Frame{
		Header: [4]byte{
			(Version&0xff00)>>8 | 0x80,
			(Version & 0x00ff),
			byte((t & 0xff00) >> 8),
			byte((t & 0x00ff) >> 0),
		},
		Flags: f,
		Data:  data,
	}
}

// DataFrame creates a data frame with the given information.
func DataFrame(streamId uint32, f FrameFlags, data []byte) Frame {
	return Frame{
		Header: [4]byte{
			byte(streamId & 0x7f000000 >> 24),
			byte(streamId & 0x00ff0000 >> 16),
			byte(streamId & 0x0000ff00 >> 8),
			byte(streamId & 0x000000ff >> 0),
		},
		Flags: f,
		Data:  data,
	}
}

// ReadFrame reads an entire frame into memory.
func ReadFrame(r io.Reader) (f Frame, err os.Error) {
	_, err = io.ReadFull(r, f.Header[:])
	if err != nil {
		return
	}
	err = binary.Read(r, binary.BigEndian, &f.Flags)
	if err != nil {
		return
	}
	var lengthField [3]byte
	_, err = io.ReadFull(r, lengthField[:])
	if err != nil {
		if err == os.EOF {
			err = io.ErrUnexpectedEOF
		}
		return
	}
	var length uint32
	length |= uint32(lengthField[0]) << 16
	length |= uint32(lengthField[1]) << 8
	length |= uint32(lengthField[2]) << 0
	if length > 0 {
		f.Data = make([]byte, int(length))
		_, err = io.ReadFull(r, f.Data)
		if err == os.EOF {
			err = io.ErrUnexpectedEOF
		}
	} else {
		f.Data = []byte{}
	}
	return
}

// IsControl returns whether the frame holds a control frame.
func (f Frame) IsControl() bool {
	return f.Header[0]&0x80 != 0
}

// Type obtains the type field if the frame is a control frame, otherwise it returns zero.
func (f Frame) Type() ControlFrameType {
	if !f.IsControl() {
		return 0
	}
	return (ControlFrameType(f.Header[2])<<8 | ControlFrameType(f.Header[3]))
}

// StreamId returns the stream ID field if the frame is a data frame, otherwise it returns zero.
func (f Frame) StreamId() (id uint32) {
	if f.IsControl() {
		return 0
	}
	id |= uint32(f.Header[0]) << 24
	id |= uint32(f.Header[1]) << 16
	id |= uint32(f.Header[2]) << 8
	id |= uint32(f.Header[3]) << 0
	return
}

// WriteTo writes the frame in the SPDY format.
func (f Frame) WriteTo(w io.Writer) (n int64, err os.Error) {
	var nn int
	// Header
	nn, err = w.Write(f.Header[:])
	n += int64(nn)
	if err != nil {
		return
	}
	// Flags
	nn, err = w.Write([]byte{byte(f.Flags)})
	n += int64(nn)
	if err != nil {
		return
	}
	// Length
	nn, err = w.Write([]byte{
		byte(len(f.Data) & 0x00ff0000 >> 16),
		byte(len(f.Data) & 0x0000ff00 >> 8),
		byte(len(f.Data) & 0x000000ff),
	})
	n += int64(nn)
	if err != nil {
		return
	}
	// Data
	if len(f.Data) > 0 {
		nn, err = w.Write(f.Data)
		n += int64(nn)
	}
	return
}

// headerDictionary is the dictionary sent to the zlib compressor/decompressor.
// Even though the specification states there is no null byte at the end, Chrome sends it.
const headerDictionary = "optionsgetheadpostputdeletetrace" +
	"acceptaccept-charsetaccept-encodingaccept-languageauthorizationexpectfromhost" +
	"if-modified-sinceif-matchif-none-matchif-rangeif-unmodifiedsince" +
	"max-forwardsproxy-authorizationrangerefererteuser-agent" +
	"100101200201202203204205206300301302303304305306307400401402403404405406407408409410411412413414415416417500501502503504505" +
	"accept-rangesageetaglocationproxy-authenticatepublicretry-after" +
	"servervarywarningwww-authenticateallowcontent-basecontent-encodingcache-control" +
	"connectiondatetrailertransfer-encodingupgradeviawarning" +
	"content-languagecontent-lengthcontent-locationcontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookie" +
	"MondayTuesdayWednesdayThursdayFridaySaturdaySunday" +
	"JanFebMarAprMayJunJulAugSepOctNovDec" +
	"chunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplication/xhtmltext/plainpublicmax-age" +
	"charset=iso-8859-1utf-8gzipdeflateHTTP/1.1statusversionurl\x00"

// hrSource is a reader that passes through reads from another reader.
// When the underlying reader reaches EOF, Read will block until another reader is added via change.
type hrSource struct {
	r io.Reader
	m sync.RWMutex
	c *sync.Cond
}

func (src *hrSource) Read(p []byte) (n int, err os.Error) {
	src.m.RLock()
	for src.r == nil {
		src.c.Wait()
	}
	n, err = src.r.Read(p)
	src.m.RUnlock()
	if err == os.EOF {
		src.change(nil)
		err = nil
	}
	return
}

func (src *hrSource) change(r io.Reader) {
	src.m.Lock()
	defer src.m.Unlock()
	src.r = r
	src.c.Broadcast()
}

// A HeaderReader reads zlib-compressed headers.
type HeaderReader struct {
	source       hrSource
	decompressor io.ReadCloser
}

// NewHeaderReader creates a HeaderReader with the initial dictionary.
func NewHeaderReader() (hr *HeaderReader) {
	hr = new(HeaderReader)
	hr.source.c = sync.NewCond(hr.source.m.RLocker())
	return
}

// ReadHeader reads a set of headers from a reader.
func (hr *HeaderReader) ReadHeader(r io.Reader) (h http.Header, err os.Error) {
	hr.source.change(r)
	h, err = hr.read()
	return
}

// Decode reads a set of headers from a block of bytes.
func (hr *HeaderReader) Decode(data []byte) (h http.Header, err os.Error) {
	hr.source.change(bytes.NewBuffer(data))
	h, err = hr.read()
	return
}

func (hr *HeaderReader) read() (h http.Header, err os.Error) {
	var count uint16
	if hr.decompressor == nil {
		hr.decompressor, err = zlib.NewReaderDict(&hr.source, []byte(headerDictionary))
		if err != nil {
			return
		}
	}
	err = binary.Read(hr.decompressor, binary.BigEndian, &count)
	if err != nil {
		return
	}
	h = make(http.Header, int(count))
	for i := 0; i < int(count); i++ {
		var name, value string
		name, err = readHeaderString(hr.decompressor)
		if err != nil {
			return
		}
		value, err = readHeaderString(hr.decompressor)
		if err != nil {
			return
		}
		valueList := strings.Split(string(value), "\x00", -1)
		for _, v := range valueList {
			h.Add(name, v)
		}
	}
	return
}

func readHeaderString(r io.Reader) (s string, err os.Error) {
	var length uint16
	err = binary.Read(r, binary.BigEndian, &length)
	if err != nil {
		return
	}
	data := make([]byte, int(length))
	_, err = io.ReadFull(r, data)
	if err != nil {
		return
	}
	return string(data), nil
}

// HeaderWriter will write zlib-compressed headers on different streams.
type HeaderWriter struct {
	compressor *zlib.Writer
	buffer     *bytes.Buffer
}

// NewHeaderWriter creates a HeaderWriter ready to compress headers.
func NewHeaderWriter(level int) (hw *HeaderWriter) {
	hw = &HeaderWriter{buffer: new(bytes.Buffer)}
	hw.compressor, _ = zlib.NewWriterDict(hw.buffer, level, []byte(headerDictionary))
	return
}

// WriteHeader writes a header block directly to an output.
func (hw *HeaderWriter) WriteHeader(w io.Writer, h http.Header) (err os.Error) {
	hw.write(h)
	_, err = io.Copy(w, hw.buffer)
	hw.buffer.Reset()
	return
}

// Encode returns a compressed header block.
func (hw *HeaderWriter) Encode(h http.Header) (data []byte) {
	hw.write(h)
	data = make([]byte, hw.buffer.Len())
	hw.buffer.Read(data)
	return
}

func (hw *HeaderWriter) write(h http.Header) {
	binary.Write(hw.compressor, binary.BigEndian, uint16(len(h)))
	for k, vals := range h {
		k = strings.ToLower(k)
		binary.Write(hw.compressor, binary.BigEndian, uint16(len(k)))
		binary.Write(hw.compressor, binary.BigEndian, []byte(k))
		v := strings.Join(vals, "\x00")
		binary.Write(hw.compressor, binary.BigEndian, uint16(len(v)))
		binary.Write(hw.compressor, binary.BigEndian, []byte(v))
	}
	hw.compressor.Flush()
}
<commit_msg>http/spdy: add type to FlagClearPreviouslyPersistedSettings constant<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package spdy is an incomplete implementation of the SPDY protocol.
//
// The implementation follows draft 2 of the spec:
// https://sites.google.com/a/chromium.org/dev/spdy/spdy-protocol/spdy-protocol-draft2
package spdy

import (
	"bytes"
	"compress/zlib"
	"encoding/binary"
	"http"
	"io"
	"os"
	"strconv"
	"strings"
	"sync"
)

// Version is the protocol version number that this package implements.
const Version = 2

// ControlFrameType stores the type field in a control frame header.
type ControlFrameType uint16

// Control frame type constants
const (
	TypeSynStream    ControlFrameType = 0x0001
	TypeSynReply     = 0x0002
	TypeRstStream    = 0x0003
	TypeSettings     = 0x0004
	TypeNoop         = 0x0005
	TypePing         = 0x0006
	TypeGoaway       = 0x0007
	TypeHeaders      = 0x0008
	TypeWindowUpdate = 0x0009
)

func (t ControlFrameType) String() string {
	switch t {
	case TypeSynStream:
		return "SYN_STREAM"
	case TypeSynReply:
		return "SYN_REPLY"
	case TypeRstStream:
		return "RST_STREAM"
	case TypeSettings:
		return "SETTINGS"
	case TypeNoop:
		return "NOOP"
	case TypePing:
		return "PING"
	case TypeGoaway:
		return "GOAWAY"
	case TypeHeaders:
		return "HEADERS"
	case TypeWindowUpdate:
		return "WINDOW_UPDATE"
	}
	return "Type(" + strconv.Itoa(int(t)) + ")"
}

type FrameFlags uint8

// Stream frame flags
const (
	FlagFin            FrameFlags = 0x01
	FlagUnidirectional = 0x02
)

// SETTINGS frame flags
const (
	FlagClearPreviouslyPersistedSettings FrameFlags = 0x01
)

// MaxDataLength is the maximum number of bytes that can be stored in one frame.
const MaxDataLength = 1<<24 - 1

// A Frame is a framed message as sent between clients and servers.
// There are two types of frames: control frames and data frames.
type Frame struct {
	Header [4]byte
	Flags  FrameFlags
	Data   []byte
}

// ControlFrame creates a control frame with the given information.
func ControlFrame(t ControlFrameType, f FrameFlags, data []byte) Frame {
	return Frame{
		Header: [4]byte{
			(Version&0xff00)>>8 | 0x80,
			(Version & 0x00ff),
			byte((t & 0xff00) >> 8),
			byte((t & 0x00ff) >> 0),
		},
		Flags: f,
		Data:  data,
	}
}

// DataFrame creates a data frame with the given information.
func DataFrame(streamId uint32, f FrameFlags, data []byte) Frame {
	return Frame{
		Header: [4]byte{
			byte(streamId & 0x7f000000 >> 24),
			byte(streamId & 0x00ff0000 >> 16),
			byte(streamId & 0x0000ff00 >> 8),
			byte(streamId & 0x000000ff >> 0),
		},
		Flags: f,
		Data:  data,
	}
}

// ReadFrame reads an entire frame into memory.
func ReadFrame(r io.Reader) (f Frame, err os.Error) {
	_, err = io.ReadFull(r, f.Header[:])
	if err != nil {
		return
	}
	err = binary.Read(r, binary.BigEndian, &f.Flags)
	if err != nil {
		return
	}
	var lengthField [3]byte
	_, err = io.ReadFull(r, lengthField[:])
	if err != nil {
		if err == os.EOF {
			err = io.ErrUnexpectedEOF
		}
		return
	}
	var length uint32
	length |= uint32(lengthField[0]) << 16
	length |= uint32(lengthField[1]) << 8
	length |= uint32(lengthField[2]) << 0
	if length > 0 {
		f.Data = make([]byte, int(length))
		_, err = io.ReadFull(r, f.Data)
		if err == os.EOF {
			err = io.ErrUnexpectedEOF
		}
	} else {
		f.Data = []byte{}
	}
	return
}

// IsControl returns whether the frame holds a control frame.
func (f Frame) IsControl() bool {
	return f.Header[0]&0x80 != 0
}

// Type obtains the type field if the frame is a control frame, otherwise it returns zero.
func (f Frame) Type() ControlFrameType {
	if !f.IsControl() {
		return 0
	}
	return (ControlFrameType(f.Header[2])<<8 | ControlFrameType(f.Header[3]))
}

// StreamId returns the stream ID field if the frame is a data frame, otherwise it returns zero.
func (f Frame) StreamId() (id uint32) {
	if f.IsControl() {
		return 0
	}
	id |= uint32(f.Header[0]) << 24
	id |= uint32(f.Header[1]) << 16
	id |= uint32(f.Header[2]) << 8
	id |= uint32(f.Header[3]) << 0
	return
}

// WriteTo writes the frame in the SPDY format.
func (f Frame) WriteTo(w io.Writer) (n int64, err os.Error) {
	var nn int
	// Header
	nn, err = w.Write(f.Header[:])
	n += int64(nn)
	if err != nil {
		return
	}
	// Flags
	nn, err = w.Write([]byte{byte(f.Flags)})
	n += int64(nn)
	if err != nil {
		return
	}
	// Length
	nn, err = w.Write([]byte{
		byte(len(f.Data) & 0x00ff0000 >> 16),
		byte(len(f.Data) & 0x0000ff00 >> 8),
		byte(len(f.Data) & 0x000000ff),
	})
	n += int64(nn)
	if err != nil {
		return
	}
	// Data
	if len(f.Data) > 0 {
		nn, err = w.Write(f.Data)
		n += int64(nn)
	}
	return
}

// headerDictionary is the dictionary sent to the zlib compressor/decompressor.
// Even though the specification states there is no null byte at the end, Chrome sends it.
const headerDictionary = "optionsgetheadpostputdeletetrace" +
	"acceptaccept-charsetaccept-encodingaccept-languageauthorizationexpectfromhost" +
	"if-modified-sinceif-matchif-none-matchif-rangeif-unmodifiedsince" +
	"max-forwardsproxy-authorizationrangerefererteuser-agent" +
	"100101200201202203204205206300301302303304305306307400401402403404405406407408409410411412413414415416417500501502503504505" +
	"accept-rangesageetaglocationproxy-authenticatepublicretry-after" +
	"servervarywarningwww-authenticateallowcontent-basecontent-encodingcache-control" +
	"connectiondatetrailertransfer-encodingupgradeviawarning" +
	"content-languagecontent-lengthcontent-locationcontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookie" +
	"MondayTuesdayWednesdayThursdayFridaySaturdaySunday" +
	"JanFebMarAprMayJunJulAugSepOctNovDec" +
	"chunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplication/xhtmltext/plainpublicmax-age" +
	"charset=iso-8859-1utf-8gzipdeflateHTTP/1.1statusversionurl\x00"

// hrSource is a reader that passes through reads from another reader.
// When the underlying reader reaches EOF, Read will block until another reader is added via change.
type hrSource struct {
	r io.Reader
	m sync.RWMutex
	c *sync.Cond
}

func (src *hrSource) Read(p []byte) (n int, err os.Error) {
	src.m.RLock()
	for src.r == nil {
		src.c.Wait()
	}
	n, err = src.r.Read(p)
	src.m.RUnlock()
	if err == os.EOF {
		src.change(nil)
		err = nil
	}
	return
}

func (src *hrSource) change(r io.Reader) {
	src.m.Lock()
	defer src.m.Unlock()
	src.r = r
	src.c.Broadcast()
}

// A HeaderReader reads zlib-compressed headers.
type HeaderReader struct {
	source       hrSource
	decompressor io.ReadCloser
}

// NewHeaderReader creates a HeaderReader with the initial dictionary.
func NewHeaderReader() (hr *HeaderReader) {
	hr = new(HeaderReader)
	hr.source.c = sync.NewCond(hr.source.m.RLocker())
	return
}

// ReadHeader reads a set of headers from a reader.
func (hr *HeaderReader) ReadHeader(r io.Reader) (h http.Header, err os.Error) {
	hr.source.change(r)
	h, err = hr.read()
	return
}

// Decode reads a set of headers from a block of bytes.
func (hr *HeaderReader) Decode(data []byte) (h http.Header, err os.Error) {
	hr.source.change(bytes.NewBuffer(data))
	h, err = hr.read()
	return
}

func (hr *HeaderReader) read() (h http.Header, err os.Error) {
	var count uint16
	if hr.decompressor == nil {
		hr.decompressor, err = zlib.NewReaderDict(&hr.source, []byte(headerDictionary))
		if err != nil {
			return
		}
	}
	err = binary.Read(hr.decompressor, binary.BigEndian, &count)
	if err != nil {
		return
	}
	h = make(http.Header, int(count))
	for i := 0; i < int(count); i++ {
		var name, value string
		name, err = readHeaderString(hr.decompressor)
		if err != nil {
			return
		}
		value, err = readHeaderString(hr.decompressor)
		if err != nil {
			return
		}
		valueList := strings.Split(string(value), "\x00", -1)
		for _, v := range valueList {
			h.Add(name, v)
		}
	}
	return
}

func readHeaderString(r io.Reader) (s string, err os.Error) {
	var length uint16
	err = binary.Read(r, binary.BigEndian, &length)
	if err != nil {
		return
	}
	data := make([]byte, int(length))
	_, err = io.ReadFull(r, data)
	if err != nil {
		return
	}
	return string(data), nil
}

// HeaderWriter will write zlib-compressed headers on different streams.
type HeaderWriter struct {
	compressor *zlib.Writer
	buffer     *bytes.Buffer
}

// NewHeaderWriter creates a HeaderWriter ready to compress headers.
func NewHeaderWriter(level int) (hw *HeaderWriter) {
	hw = &HeaderWriter{buffer: new(bytes.Buffer)}
	hw.compressor, _ = zlib.NewWriterDict(hw.buffer, level, []byte(headerDictionary))
	return
}

// WriteHeader writes a header block directly to an output.
func (hw *HeaderWriter) WriteHeader(w io.Writer, h http.Header) (err os.Error) {
	hw.write(h)
	_, err = io.Copy(w, hw.buffer)
	hw.buffer.Reset()
	return
}

// Encode returns a compressed header block.
func (hw *HeaderWriter) Encode(h http.Header) (data []byte) {
	hw.write(h)
	data = make([]byte, hw.buffer.Len())
	hw.buffer.Read(data)
	return
}

func (hw *HeaderWriter) write(h http.Header) {
	binary.Write(hw.compressor, binary.BigEndian, uint16(len(h)))
	for k, vals := range h {
		k = strings.ToLower(k)
		binary.Write(hw.compressor, binary.BigEndian, uint16(len(k)))
		binary.Write(hw.compressor, binary.BigEndian, []byte(k))
		v := strings.Join(vals, "\x00")
		binary.Write(hw.compressor, binary.BigEndian, uint16(len(v)))
		binary.Write(hw.compressor, binary.BigEndian, []byte(v))
	}
	hw.compressor.Flush()
}
<|endoftext|>


<commit_before>// Copyright Project Harbor 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 retention

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/goharbor/harbor/src/jobservice/job"
	"github.com/goharbor/harbor/src/jobservice/logger"
	"github.com/goharbor/harbor/src/pkg/retention/dep"
	"github.com/goharbor/harbor/src/pkg/retention/policy"
	"github.com/goharbor/harbor/src/pkg/retention/policy/action"
	"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
	"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
	"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestps"
	"github.com/goharbor/harbor/src/pkg/retention/res"
	"github.com/goharbor/harbor/src/pkg/retention/res/selectors/doublestar"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

// JobTestSuite is test suite for testing job
type JobTestSuite struct {
	suite.Suite

	oldClient dep.Client
}

// TestJob is entry of running JobTestSuite
func TestJob(t *testing.T) {
	suite.Run(t, new(JobTestSuite))
}

// SetupSuite ...
func (suite *JobTestSuite) SetupSuite() {
	suite.oldClient = dep.DefaultClient
	dep.DefaultClient = &fakeRetentionClient{}
}

// TearDownSuite ...
func (suite *JobTestSuite) TearDownSuite() {
	dep.DefaultClient = suite.oldClient
}

func (suite *JobTestSuite) TestRunSuccess() {
	params := make(job.Parameters)
	params[ParamDryRun] = false
	repository := &res.Repository{
		Namespace: "library",
		Name:      "harbor",
		Kind:      res.Image,
	}
	repoJSON, err := repository.ToJSON()
	require.Nil(suite.T(), err)
	params[ParamRepo] = repoJSON

	scopeSelectors := make(map[string][]*rule.Selector)
	scopeSelectors["project"] = []*rule.Selector{{
		Kind:       doublestar.Kind,
		Decoration: doublestar.RepoMatches,
		Pattern:    "{harbor}",
	}}

	ruleParams := make(rule.Parameters)
	ruleParams[latestps.ParameterK] = 10

	meta := &lwp.Metadata{
		Algorithm: policy.AlgorithmOR,
		Rules: []*rule.Metadata{
			{
				ID:         1,
				Priority:   999,
				Action:     action.Retain,
				Template:   latestps.TemplateID,
				Parameters: ruleParams,
				TagSelectors: []*rule.Selector{{
					Kind:       doublestar.Kind,
					Decoration: doublestar.Matches,
					Pattern:    "**",
				}},
				ScopeSelectors: scopeSelectors,
			},
		},
	}
	metaJSON, err := meta.ToJSON()
	require.Nil(suite.T(), err)
	params[ParamMeta] = metaJSON

	j := &Job{}
	err = j.Validate(params)
	require.NoError(suite.T(), err)

	err = j.Run(&fakeJobContext{}, params)
	require.NoError(suite.T(), err)
}

type fakeRetentionClient struct{}

// GetCandidates ...
func (frc *fakeRetentionClient) GetCandidates(repo *res.Repository) ([]*res.Candidate, error) {
	return []*res.Candidate{
		{
			Namespace:    "library",
			Repository:   "harbor",
			Kind:         "image",
			Tag:          "latest",
			PushedTime:   time.Now().Unix() - 11,
			PulledTime:   time.Now().Unix() - 2,
			CreationTime: time.Now().Unix() - 10,
			Labels:       []string{"L1", "L2"},
		},
		{
			Namespace:    "library",
			Repository:   "harbor",
			Kind:         "image",
			Tag:          "dev",
			PushedTime:   time.Now().Unix() - 10,
			PulledTime:   time.Now().Unix() - 3,
			CreationTime: time.Now().Unix() - 20,
			Labels:       []string{"L3"},
		},
	}, nil
}

// Delete ...
func (frc *fakeRetentionClient) Delete(candidate *res.Candidate) error {
	return nil
}

// SubmitTask ...
func (frc *fakeRetentionClient) DeleteRepository(repo *res.Repository) error {
	return nil
}

type fakeLogger struct{}

// For debuging
func (l *fakeLogger) Debug(v ...interface{}) {}

// For debuging with format
func (l *fakeLogger) Debugf(format string, v ...interface{}) {}

// For logging info
func (l *fakeLogger) Info(v ...interface{}) {
	fmt.Println(v...)
}

// For logging info with format
func (l *fakeLogger) Infof(format string, v ...interface{}) {
	fmt.Printf(format+"\n", v...)
}

// For warning
func (l *fakeLogger) Warning(v ...interface{}) {}

// For warning with format
func (l *fakeLogger) Warningf(format string, v ...interface{}) {}

// For logging error
func (l *fakeLogger) Error(v ...interface{}) {
	fmt.Println(v...)
}

// For logging error with format
func (l *fakeLogger) Errorf(format string, v ...interface{}) {
}

// For fatal error
func (l *fakeLogger) Fatal(v ...interface{}) {}

// For fatal error with error
func (l *fakeLogger) Fatalf(format string, v ...interface{}) {}

type fakeJobContext struct{}

func (c *fakeJobContext) Build(tracker job.Tracker) (job.Context, error) {
	return nil, nil
}

func (c *fakeJobContext) Get(prop string) (interface{}, bool) {
	return nil, false
}

func (c *fakeJobContext) SystemContext() context.Context {
	return context.TODO()
}

func (c *fakeJobContext) Checkin(status string) error {
	fmt.Printf("Check in: %s\n", status)

	return nil
}

func (c *fakeJobContext) OPCommand() (job.OPCommand, bool) {
	return "", false
}

func (c *fakeJobContext) GetLogger() logger.Interface {
	return &fakeLogger{}
}

func (c *fakeJobContext) Tracker() job.Tracker {
	return nil
}
<commit_msg>prevent retained tag with same digest deleted by other tag<commit_after>// Copyright Project Harbor 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 retention

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/goharbor/harbor/src/jobservice/job"
	"github.com/goharbor/harbor/src/jobservice/logger"
	"github.com/goharbor/harbor/src/pkg/retention/dep"
	"github.com/goharbor/harbor/src/pkg/retention/policy"
	"github.com/goharbor/harbor/src/pkg/retention/policy/action"
	"github.com/goharbor/harbor/src/pkg/retention/policy/lwp"
	"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
	"github.com/goharbor/harbor/src/pkg/retention/policy/rule/latestps"
	"github.com/goharbor/harbor/src/pkg/retention/res"
	"github.com/goharbor/harbor/src/pkg/retention/res/selectors/doublestar"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

// JobTestSuite is test suite for testing job
type JobTestSuite struct {
	suite.Suite

	oldClient dep.Client
}

// TestJob is entry of running JobTestSuite
func TestJob(t *testing.T) {
	suite.Run(t, new(JobTestSuite))
}

// SetupSuite ...
func (suite *JobTestSuite) SetupSuite() {
	suite.oldClient = dep.DefaultClient
	dep.DefaultClient = &fakeRetentionClient{}
}

// TearDownSuite ...
func (suite *JobTestSuite) TearDownSuite() {
	dep.DefaultClient = suite.oldClient
}

func (suite *JobTestSuite) TestRunSuccess() {
	params := make(job.Parameters)
	params[ParamDryRun] = false
	repository := &res.Repository{
		Namespace: "library",
		Name:      "harbor",
		Kind:      res.Image,
	}
	repoJSON, err := repository.ToJSON()
	require.Nil(suite.T(), err)
	params[ParamRepo] = repoJSON

	scopeSelectors := make(map[string][]*rule.Selector)
	scopeSelectors["project"] = []*rule.Selector{{
		Kind:       doublestar.Kind,
		Decoration: doublestar.RepoMatches,
		Pattern:    "{harbor}",
	}}

	ruleParams := make(rule.Parameters)
	ruleParams[latestps.ParameterK] = 10

	meta := &lwp.Metadata{
		Algorithm: policy.AlgorithmOR,
		Rules: []*rule.Metadata{
			{
				ID:         1,
				Priority:   999,
				Action:     action.Retain,
				Template:   latestps.TemplateID,
				Parameters: ruleParams,
				TagSelectors: []*rule.Selector{{
					Kind:       doublestar.Kind,
					Decoration: doublestar.Matches,
					Pattern:    "**",
				}},
				ScopeSelectors: scopeSelectors,
			},
		},
	}
	metaJSON, err := meta.ToJSON()
	require.Nil(suite.T(), err)
	params[ParamMeta] = metaJSON

	j := &Job{}
	err = j.Validate(params)
	require.NoError(suite.T(), err)

	err = j.Run(&fakeJobContext{}, params)
	require.NoError(suite.T(), err)
}

type fakeRetentionClient struct{}

// GetCandidates ...
func (frc *fakeRetentionClient) GetCandidates(repo *res.Repository) ([]*res.Candidate, error) {
	return []*res.Candidate{
		{
			Namespace:    "library",
			Repository:   "harbor",
			Kind:         "image",
			Tag:          "latest",
			Digest:       "latest",
			PushedTime:   time.Now().Unix() - 11,
			PulledTime:   time.Now().Unix() - 2,
			CreationTime: time.Now().Unix() - 10,
			Labels:       []string{"L1", "L2"},
		},
		{
			Namespace:    "library",
			Repository:   "harbor",
			Kind:         "image",
			Tag:          "dev",
			Digest:       "dev",
			PushedTime:   time.Now().Unix() - 10,
			PulledTime:   time.Now().Unix() - 3,
			CreationTime: time.Now().Unix() - 20,
			Labels:       []string{"L3"},
		},
	}, nil
}

// Delete ...
func (frc *fakeRetentionClient) Delete(candidate *res.Candidate) error {
	return nil
}

// SubmitTask ...
func (frc *fakeRetentionClient) DeleteRepository(repo *res.Repository) error {
	return nil
}

type fakeLogger struct{}

// For debuging
func (l *fakeLogger) Debug(v ...interface{}) {}

// For debuging with format
func (l *fakeLogger) Debugf(format string, v ...interface{}) {}

// For logging info
func (l *fakeLogger) Info(v ...interface{}) {
	fmt.Println(v...)
}

// For logging info with format
func (l *fakeLogger) Infof(format string, v ...interface{}) {
	fmt.Printf(format+"\n", v...)
}

// For warning
func (l *fakeLogger) Warning(v ...interface{}) {}

// For warning with format
func (l *fakeLogger) Warningf(format string, v ...interface{}) {}

// For logging error
func (l *fakeLogger) Error(v ...interface{}) {
	fmt.Println(v...)
}

// For logging error with format
func (l *fakeLogger) Errorf(format string, v ...interface{}) {
}

// For fatal error
func (l *fakeLogger) Fatal(v ...interface{}) {}

// For fatal error with error
func (l *fakeLogger) Fatalf(format string, v ...interface{}) {}

type fakeJobContext struct{}

func (c *fakeJobContext) Build(tracker job.Tracker) (job.Context, error) {
	return nil, nil
}

func (c *fakeJobContext) Get(prop string) (interface{}, bool) {
	return nil, false
}

func (c *fakeJobContext) SystemContext() context.Context {
	return context.TODO()
}

func (c *fakeJobContext) Checkin(status string) error {
	fmt.Printf("Check in: %s\n", status)

	return nil
}

func (c *fakeJobContext) OPCommand() (job.OPCommand, bool) {
	return "", false
}

func (c *fakeJobContext) GetLogger() logger.Interface {
	return &fakeLogger{}
}

func (c *fakeJobContext) Tracker() job.Tracker {
	return nil
}
<|endoftext|>


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

package runtime_test

import (
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"testing"
	"text/template"
)

type crashTest struct {
	Cgo bool
}

// This test is a separate program, because it is testing
// both main (m0) and non-main threads (m).

func testCrashHandler(t *testing.T, ct *crashTest) {
	if runtime.GOOS == "freebsd" {
		// TODO(brainman): do not know why this test fails on freebsd
		t.Logf("skipping test on %q", runtime.GOOS)
		return
	}

	st := template.Must(template.New("crashSource").Parse(crashSource))

	dir, err := ioutil.TempDir("", "go-build")
	if err != nil {
		t.Fatalf("failed to create temp directory: %v", err)
	}
	defer os.RemoveAll(dir)

	src := filepath.Join(dir, "main.go")
	f, err := os.Create(src)
	if err != nil {
		t.Fatalf("failed to create %v: %v", src, err)
	}
	err = st.Execute(f, ct)
	if err != nil {
		f.Close()
		t.Fatalf("failed to execute template: %v", err)
	}
	f.Close()

	got, err := exec.Command("go", "run", src).CombinedOutput()
	if err != nil {
		t.Fatalf("program exited with error: %v\n%v", err, string(got))
	}
	want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
	if string(got) != string(want) {
		t.Fatalf("expected %q, but got %q", string(want), string(got))
	}
}

func TestCrashHandler(t *testing.T) {
	testCrashHandler(t, &crashTest{Cgo: false})
}

const crashSource = `
package main

import (
	"fmt"
	"runtime"
)

{{if .Cgo}}
import "C"
{{end}}

func test(name string) {
	defer func() {
		if x := recover(); x != nil {
			fmt.Printf(" recovered")
		}
		fmt.Printf(" done\n")
	}()
	fmt.Printf("%s:", name)
	var s *string
	_ = *s
	fmt.Print("SHOULD NOT BE HERE")
}

func testInNewThread(name string) {
	c := make(chan bool)
	go func() {
		runtime.LockOSThread()
		test(name)
		c <- true
	}()
	<-c
}

func main() {
	runtime.LockOSThread()
	test("main")
	testInNewThread("new-thread")
	testInNewThread("second-new-thread")
	test("main-again")
}
`
<commit_msg>runtime: re-enable crash test on FreeBSD<commit_after>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime_test

import (
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"testing"
	"text/template"
)

type crashTest struct {
	Cgo bool
}

// This test is a separate program, because it is testing
// both main (m0) and non-main threads (m).

func testCrashHandler(t *testing.T, ct *crashTest) {
	st := template.Must(template.New("crashSource").Parse(crashSource))

	dir, err := ioutil.TempDir("", "go-build")
	if err != nil {
		t.Fatalf("failed to create temp directory: %v", err)
	}
	defer os.RemoveAll(dir)

	src := filepath.Join(dir, "main.go")
	f, err := os.Create(src)
	if err != nil {
		t.Fatalf("failed to create %v: %v", src, err)
	}
	err = st.Execute(f, ct)
	if err != nil {
		f.Close()
		t.Fatalf("failed to execute template: %v", err)
	}
	f.Close()

	got, err := exec.Command("go", "run", src).CombinedOutput()
	if err != nil {
		t.Fatalf("program exited with error: %v\n%v", err, string(got))
	}
	want := "main: recovered done\nnew-thread: recovered done\nsecond-new-thread: recovered done\nmain-again: recovered done\n"
	if string(got) != string(want) {
		t.Fatalf("expected %q, but got %q", string(want), string(got))
	}
}

func TestCrashHandler(t *testing.T) {
	testCrashHandler(t, &crashTest{Cgo: false})
}

const crashSource = `
package main

import (
	"fmt"
	"runtime"
)

{{if .Cgo}}
import "C"
{{end}}

func test(name string) {
	defer func() {
		if x := recover(); x != nil {
			fmt.Printf(" recovered")
		}
		fmt.Printf(" done\n")
	}()
	fmt.Printf("%s:", name)
	var s *string
	_ = *s
	fmt.Print("SHOULD NOT BE HERE")
}

func testInNewThread(name string) {
	c := make(chan bool)
	go func() {
		runtime.LockOSThread()
		test(name)
		c <- true
	}()
	<-c
}

func main() {
	runtime.LockOSThread()
	test("main")
	testInNewThread("new-thread")
	testInNewThread("second-new-thread")
	test("main-again")
}
`
<|endoftext|>


<commit_before>/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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 main

/* This file will handle all file related business. It will explore the file
 * system for new files and will watch files for changes. In particular, this
 * file is the gate to the files table in the database. Other components (Parser)
 * should rely on this file to know if a file was explored or not. */

import (
	fsnotify "gopkg.in/fsnotify.v1"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"sync"
)

const validCString string = `^[^\.].*\.c$`
const validHString string = `^[^\.].*\.h$`

var files chan string
var wg sync.WaitGroup
var watcher *fsnotify.Watcher
var writer chan *WriterDB

func uptodateFile(file string) bool {
	wr := <-writer
	defer func() { writer <- wr }()

	exist, uptodate, fi, err := wr.UptodateFile(file)

	if err != nil {
		// if there is an error with the dependency, we are going to
		// pretend everything is fine so the parser is not executed
		return true
	}

	if exist && uptodate {
		return true
	} else {
		wr.RemoveFileReferences(file)
		wr.InsertFile(file, fi)
		return false
	}
}

func processFile(parser *Parser) {
	wg.Add(1)
	defer wg.Done()

	// start exploring files
	for {
		file, ok := <-files

		if !ok {
			return
		}

		if !uptodateFile(file) {
			log.Println("parsing", file)
			parser.Parse(file)
		}
	}
}

func traversePath(path string, visitDir func(string), visitC func(string)) {
	filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Println("error opening ", path, " igoring")
			return filepath.SkipDir
		}

		// visit file
		if info.IsDir() {
			if info.Name()[0] == '.' {
				return filepath.SkipDir
			} else {
				visitDir(path)
				return nil
			}
		} else {
			// ignore non-C files
			validC, _ := regexp.MatchString(validCString, path)
			if validC {
				visitC(path)
			}
		}

		return nil
	})
}

func removeFileAndReparseDepends(file string, db *WriterDB) {
	deps := db.RemoveFileDepsReferences(file)
	db.RemoveFileReferences(file)

	for _, d := range deps {
		files <- d
	}
}

func handleFileChange(event fsnotify.Event) {

	validC, _ := regexp.MatchString(validCString, event.Name)
	validH, _ := regexp.MatchString(validHString, event.Name)

	db := <-writer
	defer func() { writer <- db }()

	switch {
	case validC:
		switch {
		case event.Op&(fsnotify.Create|fsnotify.Write) != 0:
			files <- event.Name
		case event.Op&(fsnotify.Remove|fsnotify.Rename) != 0:
			db.RemoveFileReferences(event.Name)
		}
	case validH:
		exist, uptodate, _, err := db.UptodateFile(event.Name)
		switch {
		case err != nil:
			return
		case event.Op&(fsnotify.Write) != 0:
			if exist && !uptodate {
				removeFileAndReparseDepends(filepath.Clean(event.Name), db)
			}
		case event.Op&(fsnotify.Remove|fsnotify.Rename) != 0:
			if exist {
				removeFileAndReparseDepends(filepath.Clean(event.Name), db)
			}
		}
	}
}

func handleDirChange(event fsnotify.Event) {
	switch {
	case event.Op&(fsnotify.Create) != 0:
		// explore the new dir
		visitorDir := func(path string) {
			// add watcher to directory
			watcher.Add(path)
		}
		visitorC := func(path string) {
			// put file in channel
			files <- path
		}
		traversePath(event.Name, visitorDir, visitorC)
	case event.Op&(fsnotify.Remove|fsnotify.Rename) != 0:
		// remove watcher from dir
		watcher.Remove(event.Name)
	}
}

func isDirectory(path string) (bool, error) {
	fi, err := os.Stat(path)
	if err != nil {
		return false, err
	} else {
		return fi.IsDir(), nil
	}
}

func handleChange(event fsnotify.Event) {

	// ignore if hidden
	if filepath.Base(event.Name)[0] == '.' {
		return
	}

	// first, we need to check if the file is a directory or not
	isDir, err := isDirectory(event.Name)
	if err != nil {
		// ignoring this event
		return
	}

	if isDir {
		handleDirChange(event)
	} else {
		handleFileChange(event)
	}
}

func StartFilesHandler(indexDir []string, nIndexingThreads int, parser *Parser,
	db *DBConnFactory) {

	files = make(chan string, nIndexingThreads)
	writer = make(chan *WriterDB, 1)
	writer <- db.NewWriter()

	// start threads to process files
	for i := 0; i < nIndexingThreads; i++ {
		go processFile(parser)
	}

	// start file watcher
	watcher, _ = fsnotify.NewWatcher()
	go func() {
		wg.Add(1)
		defer wg.Done()
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}
				handleChange(event)
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}

				log.Println("watcher error: ", err)
			}
		}
	}()

	// explore all the paths in indexDir and process all files
	rd := db.NewReader()
	removedFilesSet := rd.GetSetFilesInDB()
	rd.Close()
	visitorDir := func(path string) {
		// add watcher to directory
		watcher.Add(path)
	}
	visitorC := func(path string) {
		// update set of removed files
		delete(removedFilesSet, path)
		// put file in channel
		files <- path
	}
	for _, path := range indexDir {
		traversePath(path, visitorDir, visitorC)
	}

	// remove from DB deleted files
	wr := <-writer
	for path := range removedFilesSet {
		wr.RemoveFileReferences(path)
	}
	writer <- wr
}

func UpdateDependency(file, dep string) bool {
	wr := <-writer
	defer func() { writer <- wr }()

	exist, uptodate, fi, err := wr.UptodateFile(dep)

	if err != nil {
		// if there is an error with the dependency, we are going to
		// pretend everything is fine so the parser move forward
		return true
	}

	if !exist {
		wr.InsertFile(dep, fi)
	} else if !uptodate {
		removeFileAndReparseDepends(dep, wr)
		files <- file
		return false
	}

	wr.InsertDependency(file, dep)
	return true
}

func CloseFilesHandler() {
	close(files)

	wr := <-writer
	wr.Close()
	close(writer)

	watcher.Close()

	wg.Wait()
}
<commit_msg>Don't skip the current directory<commit_after>/*
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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 main

/* This file will handle all file related business. It will explore the file
 * system for new files and will watch files for changes. In particular, this
 * file is the gate to the files table in the database. Other components (Parser)
 * should rely on this file to know if a file was explored or not. */

import (
	fsnotify "gopkg.in/fsnotify.v1"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"sync"
)

const validCString string = `^[^\.].*\.c$`
const validHString string = `^[^\.].*\.h$`

var files chan string
var wg sync.WaitGroup
var watcher *fsnotify.Watcher
var writer chan *WriterDB

func uptodateFile(file string) bool {
	wr := <-writer
	defer func() { writer <- wr }()

	exist, uptodate, fi, err := wr.UptodateFile(file)

	if err != nil {
		// if there is an error with the dependency, we are going to
		// pretend everything is fine so the parser is not executed
		return true
	}

	if exist && uptodate {
		return true
	} else {
		wr.RemoveFileReferences(file)
		wr.InsertFile(file, fi)
		return false
	}
}

func processFile(parser *Parser) {
	wg.Add(1)
	defer wg.Done()

	// start exploring files
	for {
		file, ok := <-files

		if !ok {
			return
		}

		if !uptodateFile(file) {
			log.Println("parsing", file)
			parser.Parse(file)
		}
	}
}

func traversePath(path string, visitDir func(string), visitC func(string)) {
	filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Println("error opening ", path, " igoring")
			return filepath.SkipDir
		}

		// visit file
		if info.IsDir() {
			if info.Name() != "." && info.Name()[0] == '.' {
				return filepath.SkipDir
			} else {
				visitDir(path)
				return nil
			}
		} else {
			// ignore non-C files
			validC, _ := regexp.MatchString(validCString, path)
			if validC {
				visitC(path)
			}
		}

		return nil
	})
}

func removeFileAndReparseDepends(file string, db *WriterDB) {
	deps := db.RemoveFileDepsReferences(file)
	db.RemoveFileReferences(file)

	for _, d := range deps {
		files <- d
	}
}

func handleFileChange(event fsnotify.Event) {

	validC, _ := regexp.MatchString(validCString, event.Name)
	validH, _ := regexp.MatchString(validHString, event.Name)

	db := <-writer
	defer func() { writer <- db }()

	switch {
	case validC:
		switch {
		case event.Op&(fsnotify.Create|fsnotify.Write) != 0:
			files <- event.Name
		case event.Op&(fsnotify.Remove|fsnotify.Rename) != 0:
			db.RemoveFileReferences(event.Name)
		}
	case validH:
		exist, uptodate, _, err := db.UptodateFile(event.Name)
		switch {
		case err != nil:
			return
		case event.Op&(fsnotify.Write) != 0:
			if exist && !uptodate {
				removeFileAndReparseDepends(filepath.Clean(event.Name), db)
			}
		case event.Op&(fsnotify.Remove|fsnotify.Rename) != 0:
			if exist {
				removeFileAndReparseDepends(filepath.Clean(event.Name), db)
			}
		}
	}
}

func handleDirChange(event fsnotify.Event) {
	switch {
	case event.Op&(fsnotify.Create) != 0:
		// explore the new dir
		visitorDir := func(path string) {
			// add watcher to directory
			watcher.Add(path)
		}
		visitorC := func(path string) {
			// put file in channel
			files <- path
		}
		traversePath(event.Name, visitorDir, visitorC)
	case event.Op&(fsnotify.Remove|fsnotify.Rename) != 0:
		// remove watcher from dir
		watcher.Remove(event.Name)
	}
}

func isDirectory(path string) (bool, error) {
	fi, err := os.Stat(path)
	if err != nil {
		return false, err
	} else {
		return fi.IsDir(), nil
	}
}

func handleChange(event fsnotify.Event) {

	// ignore if hidden
	if filepath.Base(event.Name)[0] == '.' {
		return
	}

	// first, we need to check if the file is a directory or not
	isDir, err := isDirectory(event.Name)
	if err != nil {
		// ignoring this event
		return
	}

	if isDir {
		handleDirChange(event)
	} else {
		handleFileChange(event)
	}
}

func StartFilesHandler(indexDir []string, nIndexingThreads int, parser *Parser,
	db *DBConnFactory) {

	files = make(chan string, nIndexingThreads)
	writer = make(chan *WriterDB, 1)
	writer <- db.NewWriter()

	// start threads to process files
	for i := 0; i < nIndexingThreads; i++ {
		go processFile(parser)
	}

	// start file watcher
	watcher, _ = fsnotify.NewWatcher()
	go func() {
		wg.Add(1)
		defer wg.Done()
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}
				handleChange(event)
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}

				log.Println("watcher error: ", err)
			}
		}
	}()

	// explore all the paths in indexDir and process all files
	rd := db.NewReader()
	removedFilesSet := rd.GetSetFilesInDB()
	rd.Close()
	visitorDir := func(path string) {
		// add watcher to directory
		watcher.Add(path)
	}
	visitorC := func(path string) {
		// update set of removed files
		delete(removedFilesSet, path)
		// put file in channel
		files <- path
	}
	for _, path := range indexDir {
		traversePath(path, visitorDir, visitorC)
	}

	// remove from DB deleted files
	wr := <-writer
	for path := range removedFilesSet {
		wr.RemoveFileReferences(path)
	}
	writer <- wr
}

func UpdateDependency(file, dep string) bool {
	wr := <-writer
	defer func() { writer <- wr }()

	exist, uptodate, fi, err := wr.UptodateFile(dep)

	if err != nil {
		// if there is an error with the dependency, we are going to
		// pretend everything is fine so the parser move forward
		return true
	}

	if !exist {
		wr.InsertFile(dep, fi)
	} else if !uptodate {
		removeFileAndReparseDepends(dep, wr)
		files <- file
		return false
	}

	wr.InsertDependency(file, dep)
	return true
}

func CloseFilesHandler() {
	close(files)

	wr := <-writer
	wr.Close()
	close(writer)

	watcher.Close()

	wg.Wait()
}
<|endoftext|>


<commit_before>package agent

import (
	"bytes"
	"fmt"
	"strings"
	"unicode/utf8"

	"github.com/pkg/errors"

	"github.com/havoc-io/mutagen/pkg/filesystem"
	"github.com/havoc-io/mutagen/pkg/mutagen"
	"github.com/havoc-io/mutagen/pkg/process"
	"github.com/havoc-io/mutagen/pkg/prompt"
	"github.com/havoc-io/mutagen/pkg/remote"
	"github.com/havoc-io/mutagen/pkg/session"
)

func connect(
	transport Transport,
	prompter string,
	cmdExe bool,
	root,
	session string,
	version session.Version,
	configuration *session.Configuration,
	alpha bool,
) (session.Endpoint, bool, bool, error) {
	// Compute the agent invocation command, relative to the user's home
	// directory on the remote. Unless we have reason to assume that this is a
	// cmd.exe environment, we construct a path using forward slashes. This will
	// work for all POSIX systems and POSIX-like environments on Windows. If we
	// know we're hitting a cmd.exe environment, then we use backslashes,
	// otherwise the invocation won't work. Watching for cmd.exe to fail on
	// commands with forward slashes is actually the way that we detect cmd.exe
	// environments.
	// HACK: We're assuming that none of these path components have spaces in
	// them, but since we control all of them, this is probably okay.
	// HACK: When invoking on Windows systems (whether inside a POSIX
	// environment or cmd.exe), we can leave the "exe" suffix off the target
	// name. Fortunately this allows us to also avoid having to try the
	// combination of forward slashes + ".exe" for Windows POSIX environments.
	pathSeparator := "/"
	if cmdExe {
		pathSeparator = "\\"
	}
	agentInvocationPath := strings.Join([]string{
		filesystem.MutagenDirectoryName,
		agentsDirectoryName,
		mutagen.Version,
		agentBaseName,
	}, pathSeparator)

	// Compute the command to invoke.
	command := fmt.Sprintf("%s %s", agentInvocationPath, ModeEndpoint)

	// Create an agent process.
	message := "Connecting to agent (POSIX)..."
	if cmdExe {
		message = "Connecting to agent (Windows)..."
	}
	if err := prompt.Message(prompter, message); err != nil {
		return nil, false, false, errors.Wrap(err, "unable to message prompter")
	}
	agentProcess, err := transport.Command(command)
	if err != nil {
		return nil, false, false, errors.Wrap(err, "unable to create agent command")
	}

	// Create a connection that wrap's the process' standard input/output.
	connection, err := process.NewConnection(agentProcess)
	if err != nil {
		return nil, false, false, errors.Wrap(err, "unable to create agent process connection")
	}

	// Redirect the process' standard error output to a buffer so that we can
	// give better feedback in errors. This might be a bit dangerous since this
	// buffer will be attached for the lifetime of the process and we don't know
	// exactly how much output will be received (and thus we could buffer a
	// large amount of it in memory), but generally speaking our transport
	// commands don't spit out too much error output, and the agent doesn't spit
	// out any.
	// TODO: If we do start seeing large allocations in these buffers, a simple
	// size-limited buffer might suffice, at least to get some of the error
	// message.
	// TODO: Since this problem will likely be shared with custom protocols
	// (which will invoke transport executables), it would be good to implement
	// a shared solution.
	errorBuffer := bytes.NewBuffer(nil)
	agentProcess.Stderr = errorBuffer

	// Start the process.
	if err = agentProcess.Start(); err != nil {
		return nil, false, false, errors.Wrap(err, "unable to start agent process")
	}

	// Wrap the connection in an endpoint client and handle errors that may have
	// arisen during the handshake process. Specifically, we look for transport
	// errors that occur during handshake, because that's an indication that our
	// agent transport process is not functioning correctly. If that's the case,
	// we wait for the agent transport process to exit (which we know it will
	// because the NewEndpointClient method will close the connection (hence
	// terminating the process) on failure), and probe the issue.
	endpoint, err := remote.NewEndpointClient(connection, root, session, version, configuration, alpha)
	if remote.IsHandshakeTransportError(err) {
		// Wait for the process to complete. We need to do this before touching
		// the error buffer because it isn't safe for concurrent usage, and
		// until Wait completes, the I/O forwarding Goroutines can still be
		// running.
		processErr := agentProcess.Wait()

		// Extract error output and ensure it's UTF-8.
		errorOutput := errorBuffer.String()
		if !utf8.ValidString(errorOutput) {
			return nil, false, false, errors.New("remote did not return UTF-8 output")
		}

		// See if we can understand the exact nature of the failure. In
		// particular, we want to identify whether or not we should try to
		// (re-)install the agent binary and whether or not we're talking to a
		// Windows cmd.exe environment. We have to map this responsibility out
		// to the transport, because each has different error classification
		// mechanisms.
		tryInstall, cmdExe, err := transport.ClassifyError(processErr, errorOutput)
		if err != nil {
			return nil, false, false, errors.Wrap(err, "unable to classify connection handshake error")
		}

		// Return what we've found.
		return nil, tryInstall, cmdExe, errors.New("unable to handshake with agent process")
	} else if err != nil {
		return nil, false, false, errors.Wrap(err, "unable to create endpoint client")
	}

	// Done.
	return endpoint, false, false, nil
}

// Dial connects to an agent-based endpoint using the specified transport,
// prompter, and endpoint metadata.
func Dial(
	transport Transport,
	prompter,
	root,
	session string,
	version session.Version,
	configuration *session.Configuration,
	alpha bool,
) (session.Endpoint, error) {
	// Attempt a connection. If this fails but we detect a Windows cmd.exe
	// environment in the process, then re-attempt a connection under the
	// cmd.exe assumption.
	endpoint, tryInstall, cmdExe, err :=
		connect(transport, prompter, false, root, session, version, configuration, alpha)
	if err == nil {
		return endpoint, nil
	} else if cmdExe {
		endpoint, tryInstall, cmdExe, err =
			connect(transport, prompter, true, root, session, version, configuration, alpha)
		if err == nil {
			return endpoint, nil
		}
	}

	// If connection attempts have failed, then check whether or not an install
	// is recommended. If not, then bail.
	if !tryInstall {
		return nil, err
	}

	// Attempt to install.
	if err := install(transport, prompter); err != nil {
		return nil, errors.Wrap(err, "unable to install agent")
	}

	// Re-attempt connectivity.
	endpoint, _, _, err = connect(transport, prompter, cmdExe, root, session, version, configuration, alpha)
	if err != nil {
		return nil, err
	}
	return endpoint, nil
}
<commit_msg>Brought back error output on dial failure.<commit_after>package agent

import (
	"bytes"
	"fmt"
	"strings"
	"unicode/utf8"

	"github.com/pkg/errors"

	"github.com/havoc-io/mutagen/pkg/filesystem"
	"github.com/havoc-io/mutagen/pkg/mutagen"
	"github.com/havoc-io/mutagen/pkg/process"
	"github.com/havoc-io/mutagen/pkg/prompt"
	"github.com/havoc-io/mutagen/pkg/remote"
	"github.com/havoc-io/mutagen/pkg/session"
)

func connect(
	transport Transport,
	prompter string,
	cmdExe bool,
	root,
	session string,
	version session.Version,
	configuration *session.Configuration,
	alpha bool,
) (session.Endpoint, bool, bool, error) {
	// Compute the agent invocation command, relative to the user's home
	// directory on the remote. Unless we have reason to assume that this is a
	// cmd.exe environment, we construct a path using forward slashes. This will
	// work for all POSIX systems and POSIX-like environments on Windows. If we
	// know we're hitting a cmd.exe environment, then we use backslashes,
	// otherwise the invocation won't work. Watching for cmd.exe to fail on
	// commands with forward slashes is actually the way that we detect cmd.exe
	// environments.
	// HACK: We're assuming that none of these path components have spaces in
	// them, but since we control all of them, this is probably okay.
	// HACK: When invoking on Windows systems (whether inside a POSIX
	// environment or cmd.exe), we can leave the "exe" suffix off the target
	// name. Fortunately this allows us to also avoid having to try the
	// combination of forward slashes + ".exe" for Windows POSIX environments.
	pathSeparator := "/"
	if cmdExe {
		pathSeparator = "\\"
	}
	agentInvocationPath := strings.Join([]string{
		filesystem.MutagenDirectoryName,
		agentsDirectoryName,
		mutagen.Version,
		agentBaseName,
	}, pathSeparator)

	// Compute the command to invoke.
	command := fmt.Sprintf("%s %s", agentInvocationPath, ModeEndpoint)

	// Create an agent process.
	message := "Connecting to agent (POSIX)..."
	if cmdExe {
		message = "Connecting to agent (Windows)..."
	}
	if err := prompt.Message(prompter, message); err != nil {
		return nil, false, false, errors.Wrap(err, "unable to message prompter")
	}
	agentProcess, err := transport.Command(command)
	if err != nil {
		return nil, false, false, errors.Wrap(err, "unable to create agent command")
	}

	// Create a connection that wrap's the process' standard input/output.
	connection, err := process.NewConnection(agentProcess)
	if err != nil {
		return nil, false, false, errors.Wrap(err, "unable to create agent process connection")
	}

	// Redirect the process' standard error output to a buffer so that we can
	// give better feedback in errors. This might be a bit dangerous since this
	// buffer will be attached for the lifetime of the process and we don't know
	// exactly how much output will be received (and thus we could buffer a
	// large amount of it in memory), but generally speaking our transport
	// commands don't spit out too much error output, and the agent doesn't spit
	// out any.
	// TODO: If we do start seeing large allocations in these buffers, a simple
	// size-limited buffer might suffice, at least to get some of the error
	// message.
	// TODO: Since this problem will likely be shared with custom protocols
	// (which will invoke transport executables), it would be good to implement
	// a shared solution.
	errorBuffer := bytes.NewBuffer(nil)
	agentProcess.Stderr = errorBuffer

	// Start the process.
	if err = agentProcess.Start(); err != nil {
		return nil, false, false, errors.Wrap(err, "unable to start agent process")
	}

	// Wrap the connection in an endpoint client and handle errors that may have
	// arisen during the handshake process. Specifically, we look for transport
	// errors that occur during handshake, because that's an indication that our
	// agent transport process is not functioning correctly. If that's the case,
	// we wait for the agent transport process to exit (which we know it will
	// because the NewEndpointClient method will close the connection (hence
	// terminating the process) on failure), and probe the issue.
	endpoint, err := remote.NewEndpointClient(connection, root, session, version, configuration, alpha)
	if remote.IsHandshakeTransportError(err) {
		// Wait for the process to complete. We need to do this before touching
		// the error buffer because it isn't safe for concurrent usage, and
		// until Wait completes, the I/O forwarding Goroutines can still be
		// running.
		processErr := agentProcess.Wait()

		// Extract error output and ensure it's UTF-8.
		errorOutput := errorBuffer.String()
		if !utf8.ValidString(errorOutput) {
			return nil, false, false, errors.New("remote did not return UTF-8 output")
		}

		// See if we can understand the exact nature of the failure. In
		// particular, we want to identify whether or not we should try to
		// (re-)install the agent binary and whether or not we're talking to a
		// Windows cmd.exe environment. We have to map this responsibility out
		// to the transport, because each has different error classification
		// mechanisms. If the transport can't figure it out but we have some
		// error output, then give it to the user, because they're probably in a
		// better place to interpret it then they are to interpret the
		// transport's reason for classification failure. If we don't have error
		// output, then just tell the user why the transport failed to classify
		// the failure.
		tryInstall, cmdExe, err := transport.ClassifyError(processErr, errorOutput)
		if err != nil {
			if errorOutput != "" {
				return nil, false, false, errors.Errorf(
					"agent handshake failed with error output: %s",
					strings.TrimSpace(errorOutput),
				)
			}
			return nil, false, false, errors.Wrap(err, "unable to classify agent handshake error")
		}

		// Return what we've found.
		return nil, tryInstall, cmdExe, errors.New("unable to handshake with agent process")
	} else if err != nil {
		return nil, false, false, errors.Wrap(err, "unable to create endpoint client")
	}

	// Done.
	return endpoint, false, false, nil
}

// Dial connects to an agent-based endpoint using the specified transport,
// prompter, and endpoint metadata.
func Dial(
	transport Transport,
	prompter,
	root,
	session string,
	version session.Version,
	configuration *session.Configuration,
	alpha bool,
) (session.Endpoint, error) {
	// Attempt a connection. If this fails but we detect a Windows cmd.exe
	// environment in the process, then re-attempt a connection under the
	// cmd.exe assumption.
	endpoint, tryInstall, cmdExe, err :=
		connect(transport, prompter, false, root, session, version, configuration, alpha)
	if err == nil {
		return endpoint, nil
	} else if cmdExe {
		endpoint, tryInstall, cmdExe, err =
			connect(transport, prompter, true, root, session, version, configuration, alpha)
		if err == nil {
			return endpoint, nil
		}
	}

	// If connection attempts have failed, then check whether or not an install
	// is recommended. If not, then bail.
	if !tryInstall {
		return nil, err
	}

	// Attempt to install.
	if err := install(transport, prompter); err != nil {
		return nil, errors.Wrap(err, "unable to install agent")
	}

	// Re-attempt connectivity.
	endpoint, _, _, err = connect(transport, prompter, cmdExe, root, session, version, configuration, alpha)
	if err != nil {
		return nil, err
	}
	return endpoint, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	fp "path/filepath"
	"strings"
)

type Dir struct {
	Root      string
	Dirs      []string
	Files     []string
	Images    []string
	AbsDirs   []string
	AbsFiles  []string
	AbsImages []string
}

func NewDir(path string) *Dir {
	fi, err := os.Stat(path)
	if err != nil {
		fmt.Println(err)
		return nil
	}
	if !fi.IsDir() {
		return nil
	} else {
		dir := Dir{Root: path}
		files, _ := ioutil.ReadDir(path)
		for _, fi := range files {
			absPath := fp.Join(path, fi.Name())
			relPath := fi.Name()
			if fi.IsDir() {
				dir.Dirs = append(dir.Dirs, relPath)
				dir.AbsDirs = append(dir.AbsDirs, absPath)
			} else {
				dir.Files = append(dir.Files, relPath)
				dir.AbsFiles = append(dir.AbsFiles, absPath)
				switch strings.ToLower(fp.Ext(relPath)) {
				case ".jpg", ".png", ".gif":
					dir.Images = append(dir.Images, relPath)
					dir.AbsImages = append(dir.AbsImages, absPath)
				default:
				}
			}
		}
		return &dir
	}
}

func size2text(size int64) string {
	size_float := float64(size)
	const ratio = 1024

	switch {
	case size < ratio:
		return fmt.Sprintf("%.2f %v", size_float, "B")
	case size/ratio < ratio:
		return fmt.Sprintf("%.2f %v", size_float/ratio, "KB")
	case size/ratio/ratio < ratio:
		return fmt.Sprintf("%.2f %v", size_float/ratio/ratio, "MB")
	case size/ratio/ratio/ratio < ratio:
		return fmt.Sprintf("%.2f %v", size_float/ratio/ratio/ratio, "GB")
	default:
		return ""
	}
}

func get_size(path string) int64 {
	fileInfo, err := os.Stat(path)
	if err != nil {
		panic(err)
	}
	return fileInfo.Size()
}

func some_files_size_int64(files []string) (total int64) {
	for _, path := range files {
		total += get_size(path)
	}
	return
}

func some_sub_dir_images_size_int64(dirs []string) (total int64) {
	for _, path := range dirs {
		tmp := NewDir(path)
		total = total + some_files_size_int64(tmp.AbsImages) + some_sub_dir_images_size_int64(tmp.AbsDirs)
	}
	return
}

func file_size_str(path string) string {
	return size2text(get_size(path))
}

func some_files_size_str(files []string) string {
	var total int64
	for _, file := range files {
		total += get_size(file)
	}
	return size2text(total)
}

func dir_images_size_str(dir string) string {
	tmp := NewDir(dir)
	return size2text(some_files_size_int64(tmp.AbsImages) + some_sub_dir_images_size_int64(tmp.AbsDirs))
}
<commit_msg>fix: add some photo extensions<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	fp "path/filepath"
	"strings"
)

type Dir struct {
	Root      string
	Dirs      []string
	Files     []string
	Images    []string
	AbsDirs   []string
	AbsFiles  []string
	AbsImages []string
}

func NewDir(path string) *Dir {
	fi, err := os.Stat(path)
	if err != nil {
		fmt.Println(err)
		return nil
	}
	if !fi.IsDir() {
		return nil
	} else {
		dir := Dir{Root: path}
		files, _ := ioutil.ReadDir(path)
		for _, fi := range files {
			absPath := fp.Join(path, fi.Name())
			relPath := fi.Name()
			if fi.IsDir() {
				dir.Dirs = append(dir.Dirs, relPath)
				dir.AbsDirs = append(dir.AbsDirs, absPath)
			} else {
				dir.Files = append(dir.Files, relPath)
				dir.AbsFiles = append(dir.AbsFiles, absPath)
				switch strings.ToLower(fp.Ext(relPath)) {
				case ".jpg", ".jpeg", ".png", ".gif", ".bmp":
					dir.Images = append(dir.Images, relPath)
					dir.AbsImages = append(dir.AbsImages, absPath)
				default:
				}
			}
		}
		return &dir
	}
}

func size2text(size int64) string {
	size_float := float64(size)
	const ratio = 1024

	switch {
	case size < ratio:
		return fmt.Sprintf("%.2f %v", size_float, "B")
	case size/ratio < ratio:
		return fmt.Sprintf("%.2f %v", size_float/ratio, "KB")
	case size/ratio/ratio < ratio:
		return fmt.Sprintf("%.2f %v", size_float/ratio/ratio, "MB")
	case size/ratio/ratio/ratio < ratio:
		return fmt.Sprintf("%.2f %v", size_float/ratio/ratio/ratio, "GB")
	default:
		return ""
	}
}

func get_size(path string) int64 {
	fileInfo, err := os.Stat(path)
	if err != nil {
		panic(err)
	}
	return fileInfo.Size()
}

func some_files_size_int64(files []string) (total int64) {
	for _, path := range files {
		total += get_size(path)
	}
	return
}

func some_sub_dir_images_size_int64(dirs []string) (total int64) {
	for _, path := range dirs {
		tmp := NewDir(path)
		total = total + some_files_size_int64(tmp.AbsImages) + some_sub_dir_images_size_int64(tmp.AbsDirs)
	}
	return
}

func file_size_str(path string) string {
	return size2text(get_size(path))
}

func some_files_size_str(files []string) string {
	var total int64
	for _, file := range files {
		total += get_size(file)
	}
	return size2text(total)
}

func dir_images_size_str(dir string) string {
	tmp := NewDir(dir)
	return size2text(some_files_size_int64(tmp.AbsImages) + some_sub_dir_images_size_int64(tmp.AbsDirs))
}
<|endoftext|>


<commit_before>// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package db

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"testing"
)

func TestBasic(t *testing.T) {
	fn := tempFile(t)
	defer os.Remove(fn)
	db, err := Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	if len(db.Records) != 0 {
		t.Fatalf("empty db contains records")
	}
	db.Save("", nil, 0)
	db.Save("1", []byte("ab"), 1)
	db.Save("23", []byte("abcd"), 2)
	checkContents := func(where string) {
		if len(db.Records) != 3 {
			t.Fatalf("bad record count %v %v, want 3", where, len(db.Records))
		}
		for key, rec := range db.Records {
			switch key {
			case "":
				if len(rec.Val) == 0 && rec.Seq == 0 {
					return
				}
			case "1":
				if bytes.Equal(rec.Val, []byte("ab")) && rec.Seq == 1 {
					return
				}
			case "23":
				if bytes.Equal(rec.Val, []byte("abcd")) && rec.Seq == 2 {
					return
				}
			default:
				t.Fatalf("unknown key: %v", key)
			}
			t.Fatalf("bad record for key %v: %+v", key, rec)
		}
	}
	checkContents("after save")
	if err := db.Flush(); err != nil {
		t.Fatalf("failed to flush db: %v", err)
	}
	checkContents("after flush")
	db, err = Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	checkContents("after reopen")
}

func TestModify(t *testing.T) {
	fn := tempFile(t)
	defer os.Remove(fn)
	db, err := Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	db.Save("1", []byte("ab"), 0)
	db.Save("23", nil, 1)
	db.Save("456", []byte("abcd"), 1)
	db.Save("7890", []byte("a"), 0)
	db.Delete("23")
	db.Save("1", nil, 5)
	db.Save("456", []byte("ef"), 6)
	db.Delete("7890")
	db.Save("456", []byte("efg"), 0)
	db.Save("7890", []byte("bc"), 0)
	checkContents := func(where string) {
		if len(db.Records) != 3 {
			t.Fatalf("bad record count %v %v, want 3", where, len(db.Records))
		}
		for key, rec := range db.Records {
			switch key {
			case "1":
				if len(rec.Val) == 0 && rec.Seq == 5 {
					return
				}
			case "456":
				if bytes.Equal(rec.Val, []byte("efg")) && rec.Seq == 0 {
					return
				}
			case "7890":
				if bytes.Equal(rec.Val, []byte("bc")) && rec.Seq == 0 {
					return
				}
			default:
				t.Fatalf("unknown key: %v", key)
			}
			t.Fatalf("bad record for key %v: %+v", key, rec)
		}
	}
	checkContents("after modification")
	if err := db.Flush(); err != nil {
		t.Fatalf("failed to flush db: %v", err)
	}
	checkContents("after flush")
	db, err = Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	checkContents("after reopen")
}

func TestLarge(t *testing.T) {
	fn := tempFile(t)
	defer os.Remove(fn)
	db, err := Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	const nrec = 1000
	val := make([]byte, 1000)
	for i := range val {
		val[i] = byte(rand.Intn(256))
	}
	for i := 0; i < nrec; i++ {
		db.Save(fmt.Sprintf("%v", i), val, 0)
	}
	if err := db.Flush(); err != nil {
		t.Fatalf("failed to flush db: %v", err)
	}
	db, err = Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	if len(db.Records) != nrec {
		t.Fatalf("wrong record count: %v, want %v", len(db.Records), nrec)
	}
}

func tempFile(t *testing.T) string {
	f, err := ioutil.TempFile("", "syzkaller.test.db")
	if err != nil {
		t.Fatalf("failed to create temp file: %v", err)
	}
	f.Close()
	return f.Name()
}
<commit_msg>pkg/db: remove code duplication in test<commit_after>// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package db

import (
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"reflect"
	"testing"
)

func TestBasic(t *testing.T) {
	fn := tempFile(t)
	defer os.Remove(fn)
	db, err := Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	if len(db.Records) != 0 {
		t.Fatalf("empty db contains records")
	}
	db.Save("", nil, 0)
	db.Save("1", []byte("ab"), 1)
	db.Save("23", []byte("abcd"), 2)

	want := map[string]Record{
		"":   {Val: nil, Seq: 0},
		"1":  {Val: []byte("ab"), Seq: 1},
		"23": {Val: []byte("abcd"), Seq: 2},
	}
	if !reflect.DeepEqual(db.Records, want) {
		t.Fatalf("bad db after save: %v, want: %v", db.Records, want)
	}
	if err := db.Flush(); err != nil {
		t.Fatalf("failed to flush db: %v", err)
	}
	if !reflect.DeepEqual(db.Records, want) {
		t.Fatalf("bad db after flush: %v, want: %v", db.Records, want)
	}
	db, err = Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	if !reflect.DeepEqual(db.Records, want) {
		t.Fatalf("bad db after reopen: %v, want: %v", db.Records, want)
	}
}

func TestModify(t *testing.T) {
	fn := tempFile(t)
	defer os.Remove(fn)
	db, err := Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	db.Save("1", []byte("ab"), 0)
	db.Save("23", nil, 1)
	db.Save("456", []byte("abcd"), 1)
	db.Save("7890", []byte("a"), 0)
	db.Delete("23")
	db.Save("1", nil, 5)
	db.Save("456", []byte("ef"), 6)
	db.Delete("7890")
	db.Save("456", []byte("efg"), 0)
	db.Save("7890", []byte("bc"), 0)

	want := map[string]Record{
		"1":    {Val: nil, Seq: 5},
		"456":  {Val: []byte("efg"), Seq: 0},
		"7890": {Val: []byte("bc"), Seq: 0},
	}
	if !reflect.DeepEqual(db.Records, want) {
		t.Fatalf("bad db after modification: %v, want: %v", db.Records, want)
	}
	if err := db.Flush(); err != nil {
		t.Fatalf("failed to flush db: %v", err)
	}
	if !reflect.DeepEqual(db.Records, want) {
		t.Fatalf("bad db after flush: %v, want: %v", db.Records, want)
	}
	db, err = Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	if !reflect.DeepEqual(db.Records, want) {
		t.Fatalf("bad db after reopen: %v, want: %v", db.Records, want)
	}
}

func TestLarge(t *testing.T) {
	fn := tempFile(t)
	defer os.Remove(fn)
	db, err := Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	const nrec = 1000
	val := make([]byte, 1000)
	for i := range val {
		val[i] = byte(rand.Intn(256))
	}
	for i := 0; i < nrec; i++ {
		db.Save(fmt.Sprintf("%v", i), val, 0)
	}
	if err := db.Flush(); err != nil {
		t.Fatalf("failed to flush db: %v", err)
	}
	db, err = Open(fn)
	if err != nil {
		t.Fatalf("failed to open db: %v", err)
	}
	if len(db.Records) != nrec {
		t.Fatalf("wrong record count: %v, want %v", len(db.Records), nrec)
	}
}

func tempFile(t *testing.T) string {
	f, err := ioutil.TempFile("", "syzkaller.test.db")
	if err != nil {
		t.Fatalf("failed to create temp file: %v", err)
	}
	f.Close()
	return f.Name()
}
<|endoftext|>


<commit_before>// Copyright 2016-2017 Authors of Cilium
//
// 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 k8s abstracts all Kubernetes specific behaviour
package k8s

import (
	goerrors "errors"
	"fmt"
	"time"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/logging/logfields"

	go_version "github.com/hashicorp/go-version"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

var (
	// ErrNilNode is returned when the Kubernetes API server has returned a nil node
	ErrNilNode = goerrors.New("API server returned nil node")

	// k8sCli is the default client.
	k8sCli = &K8sClient{}
)

// CreateConfig creates a rest.Config for a given endpoint using a kubeconfig file.
func createConfig(endpoint, kubeCfgPath string) (*rest.Config, error) {
	// If the endpoint and the kubeCfgPath are empty then we can try getting
	// the rest.Config from the InClusterConfig
	if endpoint == "" && kubeCfgPath == "" {
		return rest.InClusterConfig()
	}

	if kubeCfgPath != "" {
		return clientcmd.BuildConfigFromFlags("", kubeCfgPath)
	}

	config := &rest.Config{Host: endpoint}
	err := rest.SetKubernetesDefaults(config)

	return config, err
}

// CreateConfigFromAgentResponse creates a client configuration from a
// models.DaemonConfigurationResponse
func CreateConfigFromAgentResponse(resp *models.DaemonConfiguration) (*rest.Config, error) {
	return createConfig(resp.Status.K8sEndpoint, resp.Status.K8sConfiguration)
}

// CreateConfig creates a client configuration based on the configured API
// server and Kubeconfig path
func CreateConfig() (*rest.Config, error) {
	return createConfig(GetAPIServer(), GetKubeconfigPath())
}

// CreateClient creates a new client to access the Kubernetes API
func CreateClient(config *rest.Config) (*kubernetes.Clientset, error) {
	cs, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	stop := make(chan struct{})
	timeout := time.NewTimer(time.Minute)
	defer timeout.Stop()
	wait.Until(func() {
		log.Info("Waiting for k8s api-server to be ready...")
		err = isConnReady(cs)
		if err == nil {
			close(stop)
			return
		}
		select {
		case <-timeout.C:
			log.WithError(err).WithField(logfields.IPAddr, config.Host).Error("Unable to contact k8s api-server")
			close(stop)
		default:
		}
	}, 5*time.Second, stop)
	if err == nil {
		log.WithField(logfields.IPAddr, config.Host).Info("Connected to k8s api-server")
	}
	return cs, err
}

// GetServerVersion returns the kubernetes api-server version.
func GetServerVersion() (ver *go_version.Version, err error) {
	sv, err := Client().Discovery().ServerVersion()
	if err != nil {
		return nil, err
	}

	// Try GitVersion first. In case of error fallback to MajorMinor
	if sv.GitVersion != "" {
		// This is a string like "v1.9.0"
		ver, err = go_version.NewVersion(sv.GitVersion)
		if err == nil {
			return ver, err
		}
	}

	if sv.Major != "" && sv.Minor != "" {
		ver, err = go_version.NewVersion(fmt.Sprintf("%s.%s", sv.Major, sv.Minor))
		if err == nil {
			return ver, nil
		}
	}

	if err != nil {
		return nil, fmt.Errorf("cannot parse k8s server version from %+v: %s", sv, err)
	}
	return nil, fmt.Errorf("cannot parse k8s server version from %+v", sv)
}

// isConnReady returns the err for the controller-manager status
func isConnReady(c *kubernetes.Clientset) error {
	_, err := c.CoreV1().ComponentStatuses().Get("controller-manager", metav1.GetOptions{})
	return err
}

// Client returns the default Kubernetes client.
func Client() *K8sClient {
	return k8sCli
}

func createDefaultClient() error {
	restConfig, err := CreateConfig()
	if err != nil {
		return fmt.Errorf("unable to create k8s client rest configuration: %s", err)
	}

	createdK8sClient, err := CreateClient(restConfig)
	if err != nil {
		return fmt.Errorf("unable to create k8s client: %s", err)
	}

	k8sCli.Interface = createdK8sClient

	return nil
}
<commit_msg>k8s: Print apiserver address in info message when connecting<commit_after>// Copyright 2016-2017 Authors of Cilium
//
// 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 k8s abstracts all Kubernetes specific behaviour
package k8s

import (
	goerrors "errors"
	"fmt"
	"time"

	"github.com/cilium/cilium/api/v1/models"
	"github.com/cilium/cilium/pkg/logging/logfields"

	go_version "github.com/hashicorp/go-version"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

var (
	// ErrNilNode is returned when the Kubernetes API server has returned a nil node
	ErrNilNode = goerrors.New("API server returned nil node")

	// k8sCli is the default client.
	k8sCli = &K8sClient{}
)

// CreateConfig creates a rest.Config for a given endpoint using a kubeconfig file.
func createConfig(endpoint, kubeCfgPath string) (*rest.Config, error) {
	// If the endpoint and the kubeCfgPath are empty then we can try getting
	// the rest.Config from the InClusterConfig
	if endpoint == "" && kubeCfgPath == "" {
		return rest.InClusterConfig()
	}

	if kubeCfgPath != "" {
		return clientcmd.BuildConfigFromFlags("", kubeCfgPath)
	}

	config := &rest.Config{Host: endpoint}
	err := rest.SetKubernetesDefaults(config)

	return config, err
}

// CreateConfigFromAgentResponse creates a client configuration from a
// models.DaemonConfigurationResponse
func CreateConfigFromAgentResponse(resp *models.DaemonConfiguration) (*rest.Config, error) {
	return createConfig(resp.Status.K8sEndpoint, resp.Status.K8sConfiguration)
}

// CreateConfig creates a client configuration based on the configured API
// server and Kubeconfig path
func CreateConfig() (*rest.Config, error) {
	return createConfig(GetAPIServer(), GetKubeconfigPath())
}

// CreateClient creates a new client to access the Kubernetes API
func CreateClient(config *rest.Config) (*kubernetes.Clientset, error) {
	cs, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	stop := make(chan struct{})
	timeout := time.NewTimer(time.Minute)
	defer timeout.Stop()
	wait.Until(func() {
		// FIXME: Use config.String() when we rebase to latest go-client
		log.WithField("host", config.Host).Info("Establishing connection to apiserver")
		err = isConnReady(cs)
		if err == nil {
			close(stop)
			return
		}
		select {
		case <-timeout.C:
			log.WithError(err).WithField(logfields.IPAddr, config.Host).Error("Unable to contact k8s api-server")
			close(stop)
		default:
		}
	}, 5*time.Second, stop)
	if err == nil {
		log.Info("Connected to apiserver")
	}
	return cs, err
}

// GetServerVersion returns the kubernetes api-server version.
func GetServerVersion() (ver *go_version.Version, err error) {
	sv, err := Client().Discovery().ServerVersion()
	if err != nil {
		return nil, err
	}

	// Try GitVersion first. In case of error fallback to MajorMinor
	if sv.GitVersion != "" {
		// This is a string like "v1.9.0"
		ver, err = go_version.NewVersion(sv.GitVersion)
		if err == nil {
			return ver, err
		}
	}

	if sv.Major != "" && sv.Minor != "" {
		ver, err = go_version.NewVersion(fmt.Sprintf("%s.%s", sv.Major, sv.Minor))
		if err == nil {
			return ver, nil
		}
	}

	if err != nil {
		return nil, fmt.Errorf("cannot parse k8s server version from %+v: %s", sv, err)
	}
	return nil, fmt.Errorf("cannot parse k8s server version from %+v", sv)
}

// isConnReady returns the err for the controller-manager status
func isConnReady(c *kubernetes.Clientset) error {
	_, err := c.CoreV1().ComponentStatuses().Get("controller-manager", metav1.GetOptions{})
	return err
}

// Client returns the default Kubernetes client.
func Client() *K8sClient {
	return k8sCli
}

func createDefaultClient() error {
	restConfig, err := CreateConfig()
	if err != nil {
		return fmt.Errorf("unable to create k8s client rest configuration: %s", err)
	}

	createdK8sClient, err := CreateClient(restConfig)
	if err != nil {
		return fmt.Errorf("unable to create k8s client: %s", err)
	}

	k8sCli.Interface = createdK8sClient

	return nil
}
<|endoftext|>


<commit_before>package assertions

import (
	"ci.guzzler.io/guzzler/corcel/core"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = FDescribe("Assertions", func() {

	key := "some:key"

	Context("Greater Than Assertion", func() {

		/*
					   Test Required
					                            INSTANCE

			                                 nil      float64    int    string-number   string
					             ACTUAL

					             float64         x          x          x          x           √

					             int             x          x          x          x           √

					             string-number   x          x          x          x           √

					             string          x          x          x          x           x

					             nil             x          x          x          x           x

		*/

		//To set further context I am making the following assumption
		//Something is greater than nil
		//nil is NOT greater than nil
		//nil is NOT greater than Something
		//string which is not a number is NOT greater than any number
		//number is NOT greater than a string which is not a number
		//Attempts will first be made to parse strings into a float64
		Context("Succeeds", func() {

			key := "some:key"

			It("When Actual is float64 and Instance is nil", func() {
				actualValue := float64(1.1)
				var instanceValue struct{}

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			It("When Actual is int and Instance is nil", func() {
				actualValue := 1
				var instanceValue struct{}

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			It("When Actual is string-number and Instance is nil", func() {
				actualValue := "1"
				var instanceValue struct{}

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			It("When Actual is string and Instance is nil", func() {
				actualValue := "a"
				var instanceValue struct{}

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			It("When Actual is float64 and Instance is float64", func() {
				actualValue := float64(5)
				instanceValue := float64(1)

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			PIt("When Actual is int and Instance is float64", func() {

			})

			PIt("When Actual is string-number and Instance is float64", func() {

			})

			PIt("When Actual is float64 and Instance is int", func() {

			})

			PIt("When Actual is int and Instance is int", func() {

			})

			PIt("When Actual is string-number and Instance is int", func() {

			})

			PIt("When Actual is string and Instance is string", func() {

			})

			PIt("When Actual is float64 and Instance is string-number", func() {

			})

			PIt("When Actual is int and Instance is string-number", func() {

			})

			PIt("When Actual is string-number and Instance is string-number", func() {

			})
		})

		Context("Fails", func() {
			PIt("When Actual is nil and Instance is nil", func() {

			})

			PIt("When Actual is nil and Instance is int", func() {

			})

			PIt("When Actual is nil and Instance is string-number", func() {

			})

			PIt("When Actual is nil and Instance is string", func() {

			})

			PIt("When Actual is nil and Instance is float64", func() {

			})

			PIt("When Actual is float64 and Instance is float64", func() {

			})

			PIt("When Actual is int and Instance is float64", func() {

			})

			PIt("When Actual is string-number and Instance is float64", func() {

			})

			PIt("When Actual is float64 and Instance is int", func() {

			})

			PIt("When Actual is float64 and Instance is int", func() {

			})

			PIt("When Actual is int and Instance is int", func() {

			})

			PIt("When Actual is string-number and Instance is int", func() {

			})

			PIt("When Actual is string and Instance is int", func() {

			})

			PIt("When Actual is string and Instance is float64", func() {

			})

			PIt("When Actual is string and Instance is string-number", func() {

			})

			PIt("When Actual is string and Instance is string", func() {

			})

			PIt("When Actual is float64 and Instance is string", func() {

			})

			PIt("When Actual is int and Instance is string", func() {

			})

			PIt("When Actual string-number int and Instance is string", func() {

			})

		})

	})

	Context("Not Equal Assertion", func() {

		It("Succeeds", func() {
			executionResult := core.ExecutionResult{
				key: 8,
			}

			assertion := NotEqualAssertion{
				Key:   key,
				Value: 7,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(true))
			Expect(result["message"]).To(BeNil())
		})

		It("Fails", func() {
			executionResult := core.ExecutionResult{
				key: 7,
			}

			assertion := NotEqualAssertion{
				Key:   key,
				Value: 7,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(false))
			Expect(result["message"]).To(Equal("FAIL: 7 does match 7"))
		})
	})

	Context("Exact Assertion", func() {
		It("Exact Assertion Succeeds", func() {
			expectedValue := 7

			executionResult := core.ExecutionResult{
				key: expectedValue,
			}

			assertion := ExactAssertion{
				Key:   key,
				Value: expectedValue,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(true))
			Expect(result["message"]).To(BeNil())
		})

		It("Exact Assertion Fails", func() {
			expectedValue := 7

			executionResult := core.ExecutionResult{
				key: 8,
			}

			assertion := ExactAssertion{
				Key:   key,
				Value: expectedValue,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(false))
			Expect(result["message"]).To(Equal("FAIL: 8 does not match 7"))
		})

		//NOTHING is currently using the message when an assertion fails but we will need
		//it for when we put the errors into the report.  One of the edge cases with the message
		//is that say the actual value was a string "7" and the expected is an int 7.  The message
		//will not include the quotes so the message would read 7 does not equal 7 as opposed
		//to "7" does not equal 7.  Notice this is a type mismatch
		PIt("Exact Assertion Fails when actual and expected are different types", func() {
			key := "some:key"
			expectedValue := 7

			executionResult := core.ExecutionResult{
				key: "7",
			}

			assertion := ExactAssertion{
				Key:   key,
				Value: expectedValue,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(false))
			Expect(result["message"]).To(Equal("FAIL: \"7\" does not match 7"))
		})
	})

})
<commit_msg>Succeeds when Actual is int and Instance is float64<commit_after>package assertions

import (
	"ci.guzzler.io/guzzler/corcel/core"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = FDescribe("Assertions", func() {

	key := "some:key"

	Context("Greater Than Assertion", func() {

		/*
					   Test Required
					                            INSTANCE

			                                 nil      float64    int    string-number   string
					             ACTUAL

					             float64         x          x          x          x           √

					             int             x          x          x          x           √

					             string-number   x          x          x          x           √

					             string          x          x          x          x           x

					             nil             x          x          x          x           x

		*/

		//To set further context I am making the following assumption
		//Something is greater than nil
		//nil is NOT greater than nil
		//nil is NOT greater than Something
		//string which is not a number is NOT greater than any number
		//number is NOT greater than a string which is not a number
		//Attempts will first be made to parse strings into a float64
		Context("Succeeds", func() {

			key := "some:key"

			It("When Actual is float64 and Instance is nil", func() {
				actualValue := float64(1.1)
				var instanceValue struct{}

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			It("When Actual is int and Instance is nil", func() {
				actualValue := 1
				var instanceValue struct{}

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			It("When Actual is string-number and Instance is nil", func() {
				actualValue := "1"
				var instanceValue struct{}

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			It("When Actual is string and Instance is nil", func() {
				actualValue := "a"
				var instanceValue struct{}

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			It("When Actual is float64 and Instance is float64", func() {
				actualValue := float64(5)
				instanceValue := float64(1)

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			It("When Actual is int and Instance is float64", func() {
				actualValue := 5
				instanceValue := float64(1)

				executionResult := core.ExecutionResult{
					key: actualValue,
				}

				assertion := GreaterThanAssertion{
					Key:   key,
					Value: instanceValue,
				}

				result := assertion.Assert(executionResult)
				Expect(result["result"]).To(Equal(true))
				Expect(result["message"]).To(BeNil())
			})

			PIt("When Actual is string-number and Instance is float64", func() {

			})

			PIt("When Actual is float64 and Instance is int", func() {

			})

			PIt("When Actual is int and Instance is int", func() {

			})

			PIt("When Actual is string-number and Instance is int", func() {

			})

			PIt("When Actual is string and Instance is string", func() {

			})

			PIt("When Actual is float64 and Instance is string-number", func() {

			})

			PIt("When Actual is int and Instance is string-number", func() {

			})

			PIt("When Actual is string-number and Instance is string-number", func() {

			})
		})

		Context("Fails", func() {
			PIt("When Actual is nil and Instance is nil", func() {

			})

			PIt("When Actual is nil and Instance is int", func() {

			})

			PIt("When Actual is nil and Instance is string-number", func() {

			})

			PIt("When Actual is nil and Instance is string", func() {

			})

			PIt("When Actual is nil and Instance is float64", func() {

			})

			PIt("When Actual is float64 and Instance is float64", func() {

			})

			PIt("When Actual is int and Instance is float64", func() {

			})

			PIt("When Actual is string-number and Instance is float64", func() {

			})

			PIt("When Actual is float64 and Instance is int", func() {

			})

			PIt("When Actual is float64 and Instance is int", func() {

			})

			PIt("When Actual is int and Instance is int", func() {

			})

			PIt("When Actual is string-number and Instance is int", func() {

			})

			PIt("When Actual is string and Instance is int", func() {

			})

			PIt("When Actual is string and Instance is float64", func() {

			})

			PIt("When Actual is string and Instance is string-number", func() {

			})

			PIt("When Actual is string and Instance is string", func() {

			})

			PIt("When Actual is float64 and Instance is string", func() {

			})

			PIt("When Actual is int and Instance is string", func() {

			})

			PIt("When Actual string-number int and Instance is string", func() {

			})

		})

	})

	Context("Not Equal Assertion", func() {

		It("Succeeds", func() {
			executionResult := core.ExecutionResult{
				key: 8,
			}

			assertion := NotEqualAssertion{
				Key:   key,
				Value: 7,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(true))
			Expect(result["message"]).To(BeNil())
		})

		It("Fails", func() {
			executionResult := core.ExecutionResult{
				key: 7,
			}

			assertion := NotEqualAssertion{
				Key:   key,
				Value: 7,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(false))
			Expect(result["message"]).To(Equal("FAIL: 7 does match 7"))
		})
	})

	Context("Exact Assertion", func() {
		It("Exact Assertion Succeeds", func() {
			expectedValue := 7

			executionResult := core.ExecutionResult{
				key: expectedValue,
			}

			assertion := ExactAssertion{
				Key:   key,
				Value: expectedValue,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(true))
			Expect(result["message"]).To(BeNil())
		})

		It("Exact Assertion Fails", func() {
			expectedValue := 7

			executionResult := core.ExecutionResult{
				key: 8,
			}

			assertion := ExactAssertion{
				Key:   key,
				Value: expectedValue,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(false))
			Expect(result["message"]).To(Equal("FAIL: 8 does not match 7"))
		})

		//NOTHING is currently using the message when an assertion fails but we will need
		//it for when we put the errors into the report.  One of the edge cases with the message
		//is that say the actual value was a string "7" and the expected is an int 7.  The message
		//will not include the quotes so the message would read 7 does not equal 7 as opposed
		//to "7" does not equal 7.  Notice this is a type mismatch
		PIt("Exact Assertion Fails when actual and expected are different types", func() {
			key := "some:key"
			expectedValue := 7

			executionResult := core.ExecutionResult{
				key: "7",
			}

			assertion := ExactAssertion{
				Key:   key,
				Value: expectedValue,
			}

			result := assertion.Assert(executionResult)
			Expect(result["result"]).To(Equal(false))
			Expect(result["message"]).To(Equal("FAIL: \"7\" does not match 7"))
		})
	})

})
<|endoftext|>