<commit_before>// Copyright (c) 2018 Cisco and/or its affiliates.
//
// 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 ifplugin_test

import (
	"git.fd.io/govpp.git/adapter/mock"
	govppapi "git.fd.io/govpp.git/api"
	"git.fd.io/govpp.git/core"
	"github.com/ligato/cn-infra/logging"
	"github.com/ligato/cn-infra/logging/logrus"
	"github.com/ligato/vpp-agent/idxvpp/nametoidx"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/interfaces"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/stats"
	"github.com/ligato/vpp-agent/plugins/vpp/ifplugin"
	"github.com/ligato/vpp-agent/plugins/vpp/ifplugin/ifaceidx"
	intf "github.com/ligato/vpp-agent/plugins/vpp/model/interfaces"
	"github.com/ligato/vpp-agent/tests/vppcallmock"
	. "github.com/onsi/gomega"
	"golang.org/x/net/context"
	"net"
	"testing"
)

func testPluginDataInitialization(t *testing.T) (*core.Connection, ifaceidx.SwIfIndexRW, *ifplugin.InterfaceStateUpdater, chan govppapi.Message, chan *intf.InterfaceNotification, error) {
	RegisterTestingT(t)

	// Initialize notification channel
	notifChan := make(chan govppapi.Message, 100)

	// Initialize index
	nameToIdx := nametoidx.NewNameToIdx(logrus.DefaultLogger(), "interface_state_test", ifaceidx.IndexMetadata)
	index := ifaceidx.NewSwIfIndex(nameToIdx)
	names := nameToIdx.ListNames()

	// Check if names were empty
	Expect(names).To(BeEmpty())

	// Create publish state function
	publishChan := make(chan *intf.InterfaceNotification, 100)
	publishIfState := func(notification *intf.InterfaceNotification) {
		t.Logf("Received notification change %v", notification)
		publishChan <- notification
	}

	// Create context
	ctx, _ := context.WithCancel(context.Background())

	// Create connection
	mockCtx := &vppcallmock.TestCtx{MockVpp: &mock.VppAdapter{}}
	connection, err := core.Connect(mockCtx.MockVpp)

	if err != nil {
		return nil, nil, nil, nil, nil, err
	}

	// Create plugin logger
	pluginLogger := logging.ForPlugin("testname", logrus.NewLogRegistry())

	// Test initialization
	ifPlugin := &ifplugin.InterfaceStateUpdater{
		Log:      pluginLogger,
		GoVppmux: connection,
	}

	err = ifPlugin.Init(
		ctx,
		index,
		notifChan,
		publishIfState)

	if err != nil {
		return nil, nil, nil, nil, nil, err
	}

	// Test after init
	err = ifPlugin.AfterInit()

	if err != nil {
		return nil, nil, nil, nil, nil, err
	}

	return connection, index, ifPlugin, notifChan, publishChan, nil
}

// Test UPDOWN notification
func TestInterfaceStateUpdaterUpDownNotif(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test", 0, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	// Test notifications
	notifChan <- &interfaces.SwInterfaceEvent{
		Pid:         0,
		SwIfIndex:   0,
		AdminUpDown: 1,
		LinkUpDown:  1,
		Deleted:     0,
	}

	var notif *intf.InterfaceNotification

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	Expect(notif.State.AdminStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_UP))
}

// Test simple counter notification
func TestInterfaceStateUpdaterVnetSimpleCounterNotif(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test", 0, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	// Test notifications
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 0, // Drop,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32768},
	}

	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 1, // Punt,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32769},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 2, // Ipv4,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32770},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 3, // Ipv6,
		Count:           3,
		FirstSwIfIndex:  0,
		Data:            []uint64{32771},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 4, // RxNoBuf,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32772},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 5, // RxMiss,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32773},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 6, // RxError,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32774},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 7, // TxError,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32775},
	}

	// Send interface event notification to propagate update from counter to publish channel
	notifChan <- &interfaces.SwInterfaceEvent{
		Pid:         0,
		SwIfIndex:   0,
		AdminUpDown: 1,
		LinkUpDown:  1,
		Deleted:     0,
	}

	var notif *intf.InterfaceNotification

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	Expect(notif.State.AdminStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_UP))
	Expect(notif.State.Statistics.DropPackets).Should(BeEquivalentTo(32768))
	Expect(notif.State.Statistics.PuntPackets).Should(BeEquivalentTo(32769))
	Expect(notif.State.Statistics.Ipv4Packets).Should(BeEquivalentTo(32770))
	Expect(notif.State.Statistics.Ipv6Packets).Should(BeEquivalentTo(32771))
	Expect(notif.State.Statistics.InNobufPackets).Should(BeEquivalentTo(32772))
	Expect(notif.State.Statistics.InMissPackets).Should(BeEquivalentTo(32773))
	Expect(notif.State.Statistics.InErrorPackets).Should(BeEquivalentTo(32774))
	Expect(notif.State.Statistics.OutErrorPackets).Should(BeEquivalentTo(32775))
}

// Test VnetIntCombined notification
func TestInterfaceStateUpdaterVnetIntCombinedNotif(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test", 0, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	index.RegisterName("test2", 1, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.2/24"},
	})

	// Test notifications
	notifChan <- &stats.VnetInterfaceCombinedCounters{
		VnetCounterType: 1, // TX
		FirstSwIfIndex:  0,
		Count:           2,
		Data: []stats.VlibCounter{
			{
				Packets: 1024,
				Bytes:   2048,
			},
			{
				Packets: 2048,
				Bytes:   20480,
			},
		},
	}

	var notif *intf.InterfaceNotification

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	Expect(notif.State.Statistics.OutPackets).Should(BeEquivalentTo(1024))
	Expect(notif.State.Statistics.OutBytes).Should(BeEquivalentTo(2048))

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	Expect(notif.State.Statistics.OutPackets).Should(BeEquivalentTo(2048))
	Expect(notif.State.Statistics.OutBytes).Should(BeEquivalentTo(20480))
}

// Test SwInterfaceDetails notification
func TestInterfaceStateUpdaterSwInterfaceDetailsNotif(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test", 0, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	// Test notifications
	hwAddr1Parse, err := net.ParseMAC("01:23:45:67:89:ab")
	Expect(err).To(BeNil())

	notifChan <- &interfaces.SwInterfaceDetails{
		InterfaceName:   []byte("if0"),
		AdminUpDown:     1,    // adm up
		LinkUpDown:      0,    // oper down
		LinkMtu:         9216, // Default MTU
		L2Address:       hwAddr1Parse,
		L2AddressLength: uint32(len(hwAddr1Parse)),
		LinkSpeed:       2, // 100MB, full duplex
	}

	var notif *intf.InterfaceNotification

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UNKNOWN))
	Expect(notif.State.AdminStatus).To(Equal(intf.InterfacesState_Interface_UP))
	Expect(notif.State.OperStatus).To(Equal(intf.InterfacesState_Interface_DOWN))
	Expect(notif.State.InternalName).To(Equal("if0"))
	Expect(notif.State.Mtu).To(BeEquivalentTo(9216))
	Expect(notif.State.PhysAddress).To(Equal("01:23:45:67:89:ab"))
	Expect(notif.State.Duplex).To(Equal(intf.InterfacesState_Interface_FULL))
	Expect(notif.State.Speed).To(BeEquivalentTo(100 * 1000000))
}

// Test deleted notification
func TestInterfaceStateUpdaterIfStateDeleted(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test", 0, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	// Test notifications
	notifChan <- &interfaces.SwInterfaceEvent{
		Pid:         0,
		SwIfIndex:   0,
		AdminUpDown: 1,
		LinkUpDown:  1,
		Deleted:     0,
	}

	var notif *intf.InterfaceNotification

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	Expect(notif.State.AdminStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_UP))

	// Unregister name
	index.UnregisterName("test")

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UNKNOWN))
	Expect(notif.State.AdminStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_DELETED))
	Expect(notif.State.OperStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_DELETED))
}
<commit_msg>Fix occasional fail of test of combined stats from VPP<commit_after>// Copyright (c) 2018 Cisco and/or its affiliates.
//
// 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 ifplugin_test

import (
	"net"
	"testing"

	"git.fd.io/govpp.git/adapter/mock"
	govppapi "git.fd.io/govpp.git/api"
	"git.fd.io/govpp.git/core"
	"github.com/ligato/cn-infra/logging"
	"github.com/ligato/cn-infra/logging/logrus"
	"github.com/ligato/vpp-agent/idxvpp/nametoidx"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/interfaces"
	"github.com/ligato/vpp-agent/plugins/vpp/binapi/stats"
	"github.com/ligato/vpp-agent/plugins/vpp/ifplugin"
	"github.com/ligato/vpp-agent/plugins/vpp/ifplugin/ifaceidx"
	intf "github.com/ligato/vpp-agent/plugins/vpp/model/interfaces"
	"github.com/ligato/vpp-agent/tests/vppcallmock"
	. "github.com/onsi/gomega"
	"golang.org/x/net/context"
)

func testPluginDataInitialization(t *testing.T) (*core.Connection, ifaceidx.SwIfIndexRW, *ifplugin.InterfaceStateUpdater, chan govppapi.Message, chan *intf.InterfaceNotification, error) {
	RegisterTestingT(t)

	// Initialize notification channel
	notifChan := make(chan govppapi.Message, 100)

	// Initialize index
	nameToIdx := nametoidx.NewNameToIdx(logrus.DefaultLogger(), "interface_state_test", ifaceidx.IndexMetadata)
	index := ifaceidx.NewSwIfIndex(nameToIdx)
	names := nameToIdx.ListNames()

	// Check if names were empty
	Expect(names).To(BeEmpty())

	// Create publish state function
	publishChan := make(chan *intf.InterfaceNotification, 100)
	publishIfState := func(notification *intf.InterfaceNotification) {
		t.Logf("Received notification change %v", notification)
		publishChan <- notification
	}

	// Create context
	ctx, _ := context.WithCancel(context.Background())

	// Create connection
	mockCtx := &vppcallmock.TestCtx{MockVpp: &mock.VppAdapter{}}
	connection, err := core.Connect(mockCtx.MockVpp)

	if err != nil {
		return nil, nil, nil, nil, nil, err
	}

	// Create plugin logger
	pluginLogger := logging.ForPlugin("testname", logrus.NewLogRegistry())

	// Test initialization
	ifPlugin := &ifplugin.InterfaceStateUpdater{
		Log:      pluginLogger,
		GoVppmux: connection,
	}

	err = ifPlugin.Init(
		ctx,
		index,
		notifChan,
		publishIfState)

	if err != nil {
		return nil, nil, nil, nil, nil, err
	}

	// Test after init
	err = ifPlugin.AfterInit()

	if err != nil {
		return nil, nil, nil, nil, nil, err
	}

	return connection, index, ifPlugin, notifChan, publishChan, nil
}

// Test UPDOWN notification
func TestInterfaceStateUpdaterUpDownNotif(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test", 0, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	// Test notifications
	notifChan <- &interfaces.SwInterfaceEvent{
		Pid:         0,
		SwIfIndex:   0,
		AdminUpDown: 1,
		LinkUpDown:  1,
		Deleted:     0,
	}

	var notif *intf.InterfaceNotification

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	Expect(notif.State.AdminStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_UP))
}

// Test simple counter notification
func TestInterfaceStateUpdaterVnetSimpleCounterNotif(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test", 0, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	// Test notifications
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 0, // Drop,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32768},
	}

	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 1, // Punt,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32769},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 2, // Ipv4,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32770},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 3, // Ipv6,
		Count:           3,
		FirstSwIfIndex:  0,
		Data:            []uint64{32771},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 4, // RxNoBuf,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32772},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 5, // RxMiss,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32773},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 6, // RxError,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32774},
	}
	notifChan <- &stats.VnetInterfaceSimpleCounters{
		VnetCounterType: 7, // TxError,
		Count:           1,
		FirstSwIfIndex:  0,
		Data:            []uint64{32775},
	}

	// Send interface event notification to propagate update from counter to publish channel
	notifChan <- &interfaces.SwInterfaceEvent{
		Pid:         0,
		SwIfIndex:   0,
		AdminUpDown: 1,
		LinkUpDown:  1,
		Deleted:     0,
	}

	var notif *intf.InterfaceNotification

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	Expect(notif.State.AdminStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_UP))
	Expect(notif.State.Statistics.DropPackets).Should(BeEquivalentTo(32768))
	Expect(notif.State.Statistics.PuntPackets).Should(BeEquivalentTo(32769))
	Expect(notif.State.Statistics.Ipv4Packets).Should(BeEquivalentTo(32770))
	Expect(notif.State.Statistics.Ipv6Packets).Should(BeEquivalentTo(32771))
	Expect(notif.State.Statistics.InNobufPackets).Should(BeEquivalentTo(32772))
	Expect(notif.State.Statistics.InMissPackets).Should(BeEquivalentTo(32773))
	Expect(notif.State.Statistics.InErrorPackets).Should(BeEquivalentTo(32774))
	Expect(notif.State.Statistics.OutErrorPackets).Should(BeEquivalentTo(32775))
}

// Test VnetIntCombined notification
func TestInterfaceStateUpdaterVnetIntCombinedNotif(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test0", 0, &intf.Interfaces_Interface{
		Name:        "test0",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	index.RegisterName("test1", 1, &intf.Interfaces_Interface{
		Name:        "test1",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.2/24"},
	})

	// Test notifications
	notifChan <- &stats.VnetInterfaceCombinedCounters{
		VnetCounterType: 1, // TX
		FirstSwIfIndex:  0,
		Count:           2,
		Data: []stats.VlibCounter{
			{Packets: 1000, Bytes: 3000},
			{Packets: 2000, Bytes: 5000},
		},
	}

	var notif *intf.InterfaceNotification
	var outPackets, outBytes uint64

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	outPackets += notif.GetState().GetStatistics().GetOutPackets()
	outBytes += notif.GetState().GetStatistics().GetOutBytes()

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	outPackets += notif.GetState().GetStatistics().GetOutPackets()
	outBytes += notif.GetState().GetStatistics().GetOutBytes()

	// NOTE: Notifications from publishChan cannot gurantee
	// to be in same order as the ones going into notifChan.
	Expect(outPackets).Should(BeEquivalentTo(3000))
	Expect(outBytes).Should(BeEquivalentTo(8000))
}

// Test SwInterfaceDetails notification
func TestInterfaceStateUpdaterSwInterfaceDetailsNotif(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test", 0, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	// Test notifications
	hwAddr1Parse, err := net.ParseMAC("01:23:45:67:89:ab")
	Expect(err).To(BeNil())

	notifChan <- &interfaces.SwInterfaceDetails{
		InterfaceName:   []byte("if0"),
		AdminUpDown:     1,    // adm up
		LinkUpDown:      0,    // oper down
		LinkMtu:         9216, // Default MTU
		L2Address:       hwAddr1Parse,
		L2AddressLength: uint32(len(hwAddr1Parse)),
		LinkSpeed:       2, // 100MB, full duplex
	}

	var notif *intf.InterfaceNotification

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UNKNOWN))
	Expect(notif.State.AdminStatus).To(Equal(intf.InterfacesState_Interface_UP))
	Expect(notif.State.OperStatus).To(Equal(intf.InterfacesState_Interface_DOWN))
	Expect(notif.State.InternalName).To(Equal("if0"))
	Expect(notif.State.Mtu).To(BeEquivalentTo(9216))
	Expect(notif.State.PhysAddress).To(Equal("01:23:45:67:89:ab"))
	Expect(notif.State.Duplex).To(Equal(intf.InterfacesState_Interface_FULL))
	Expect(notif.State.Speed).To(BeEquivalentTo(100 * 1000000))
}

// Test deleted notification
func TestInterfaceStateUpdaterIfStateDeleted(t *testing.T) {
	conn, index, ifPlugin, notifChan, publishChan, err := testPluginDataInitialization(t)
	Expect(err).To(BeNil())
	defer ifPlugin.Close()
	defer conn.Disconnect()

	// Register name
	index.RegisterName("test", 0, &intf.Interfaces_Interface{
		Name:        "test",
		Enabled:     true,
		Type:        intf.InterfaceType_MEMORY_INTERFACE,
		IpAddresses: []string{"192.168.0.1/24"},
	})

	// Test notifications
	notifChan <- &interfaces.SwInterfaceEvent{
		Pid:         0,
		SwIfIndex:   0,
		AdminUpDown: 1,
		LinkUpDown:  1,
		Deleted:     0,
	}

	var notif *intf.InterfaceNotification

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UPDOWN))
	Expect(notif.State.AdminStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_UP))

	// Unregister name
	index.UnregisterName("test")

	Eventually(publishChan).Should(Receive(&notif))
	Expect(notif.Type).To(Equal(intf.InterfaceNotification_UNKNOWN))
	Expect(notif.State.AdminStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_DELETED))
	Expect(notif.State.OperStatus).Should(BeEquivalentTo(intf.InterfacesState_Interface_DELETED))
}
<|endoftext|>


<commit_before>// +build !confonly

package reverse

import (
	"context"
	"time"

	"github.com/golang/protobuf/proto"
	"v2ray.com/core/common/mux"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/session"
	"v2ray.com/core/common/task"
	"v2ray.com/core/features/routing"
	"v2ray.com/core/transport"
	"v2ray.com/core/transport/pipe"
)

// Bridge is a component in reverse proxy, that relays connections from Portal to local address.
type Bridge struct {
	dispatcher  routing.Dispatcher
	tag         string
	domain      string
	workers     []*BridgeWorker
	monitorTask *task.Periodic
}

// NewBridge creates a new Bridge instance.
func NewBridge(config *BridgeConfig, dispatcher routing.Dispatcher) (*Bridge, error) {
	if config.Tag == "" {
		return nil, newError("bridge tag is empty")
	}
	if config.Domain == "" {
		return nil, newError("bridge domain is empty")
	}

	b := &Bridge{
		dispatcher: dispatcher,
		tag:        config.Tag,
		domain:     config.Domain,
	}
	b.monitorTask = &task.Periodic{
		Execute:  b.monitor,
		Interval: time.Second * 2,
	}
	return b, nil
}

func (b *Bridge) cleanup() {
	var activeWorkers []*BridgeWorker

	for _, w := range b.workers {
		if w.IsActive() {
			activeWorkers = append(activeWorkers, w)
		}
	}

	if len(activeWorkers) != len(b.workers) {
		b.workers = activeWorkers
	}
}

func (b *Bridge) monitor() error {
	b.cleanup()

	var numConnections uint32
	var numWorker uint32

	for _, w := range b.workers {
		if w.IsActive() {
			numConnections += w.Connections()
			numWorker++
		}
	}

	if numWorker == 0 || numConnections/numWorker > 16 {
		worker, err := NewBridgeWorker(b.domain, b.tag, b.dispatcher)
		if err != nil {
			newError("failed to create bridge worker").Base(err).AtWarning().WriteToLog()
			return nil
		}
		b.workers = append(b.workers, worker)
	}

	return nil
}

func (b *Bridge) Start() error {
	return b.monitorTask.Start()
}

func (b *Bridge) Close() error {
	return b.monitorTask.Close()
}

type BridgeWorker struct {
	tag        string
	worker     *mux.ServerWorker
	dispatcher routing.Dispatcher
	state      Control_State
}

func NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWorker, error) {
	ctx := context.Background()
	ctx = session.ContextWithInbound(ctx, &session.Inbound{
		Tag: tag,
	})
	link, err := d.Dispatch(ctx, net.Destination{
		Network: net.Network_TCP,
		Address: net.DomainAddress(domain),
		Port:    0,
	})
	if err != nil {
		return nil, err
	}

	w := &BridgeWorker{
		dispatcher: d,
		tag:        tag,
	}

	worker, err := mux.NewServerWorker(context.Background(), w, link)
	if err != nil {
		return nil, err
	}
	w.worker = worker

	return w, nil
}

func (w *BridgeWorker) Type() interface{} {
	return routing.DispatcherType()
}

func (w *BridgeWorker) Start() error {
	return nil
}

func (w *BridgeWorker) Close() error {
	return nil
}

func (w *BridgeWorker) IsActive() bool {
	return w.state == Control_ACTIVE && !w.worker.Closed()
}

func (w *BridgeWorker) Connections() uint32 {
	return w.worker.ActiveConnections()
}

func (w *BridgeWorker) handleInternalConn(link transport.Link) {
	go func() {
		reader := link.Reader
		for {
			mb, err := reader.ReadMultiBuffer()
			if err != nil {
				break
			}
			for _, b := range mb {
				var ctl Control
				if err := proto.Unmarshal(b.Bytes(), &ctl); err != nil {
					newError("failed to parse proto message").Base(err).WriteToLog()
					break
				}
				if ctl.State != w.state {
					w.state = ctl.State
				}
			}
		}
	}()
}

func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {
	if !isInternalDomain(dest) {
		ctx = session.ContextWithInbound(ctx, &session.Inbound{
			Tag: w.tag,
		})
		return w.dispatcher.Dispatch(ctx, dest)
	}

	opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)}
	uplinkReader, uplinkWriter := pipe.New(opt...)
	downlinkReader, downlinkWriter := pipe.New(opt...)

	w.handleInternalConn(transport.Link{
		Reader: downlinkReader,
		Writer: uplinkWriter,
	})

	return &transport.Link{
		Reader: uplinkReader,
		Writer: downlinkWriter,
	}, nil
}
<commit_msg>Fix reverse proxy with the HTTP optimization<commit_after>// +build !confonly

package reverse

import (
	"context"
	"time"

	"github.com/golang/protobuf/proto"
	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/mux"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/session"
	"v2ray.com/core/common/task"
	"v2ray.com/core/features/routing"
	"v2ray.com/core/transport"
	"v2ray.com/core/transport/pipe"
)

// Bridge is a component in reverse proxy, that relays connections from Portal to local address.
type Bridge struct {
	dispatcher  routing.Dispatcher
	tag         string
	domain      string
	workers     []*BridgeWorker
	monitorTask *task.Periodic
}

// NewBridge creates a new Bridge instance.
func NewBridge(config *BridgeConfig, dispatcher routing.Dispatcher) (*Bridge, error) {
	if config.Tag == "" {
		return nil, newError("bridge tag is empty")
	}
	if config.Domain == "" {
		return nil, newError("bridge domain is empty")
	}

	b := &Bridge{
		dispatcher: dispatcher,
		tag:        config.Tag,
		domain:     config.Domain,
	}
	b.monitorTask = &task.Periodic{
		Execute:  b.monitor,
		Interval: time.Second * 2,
	}
	return b, nil
}

func (b *Bridge) cleanup() {
	var activeWorkers []*BridgeWorker

	for _, w := range b.workers {
		if w.IsActive() {
			activeWorkers = append(activeWorkers, w)
		}
	}

	if len(activeWorkers) != len(b.workers) {
		b.workers = activeWorkers
	}
}

func (b *Bridge) monitor() error {
	b.cleanup()

	var numConnections uint32
	var numWorker uint32

	for _, w := range b.workers {
		if w.IsActive() {
			numConnections += w.Connections()
			numWorker++
		}
	}

	if numWorker == 0 || numConnections/numWorker > 16 {
		worker, err := NewBridgeWorker(b.domain, b.tag, b.dispatcher)
		if err != nil {
			newError("failed to create bridge worker").Base(err).AtWarning().WriteToLog()
			return nil
		}
		b.workers = append(b.workers, worker)
	}

	return nil
}

func (b *Bridge) Start() error {
	return b.monitorTask.Start()
}

func (b *Bridge) Close() error {
	return b.monitorTask.Close()
}

type BridgeWorker struct {
	tag        string
	worker     *mux.ServerWorker
	dispatcher routing.Dispatcher
	state      Control_State
}

func NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWorker, error) {
	ctx := context.Background()
	ctx = session.ContextWithInbound(ctx, &session.Inbound{
		Tag: tag,
	})
	link, err := d.Dispatch(ctx, net.Destination{
		Network: net.Network_TCP,
		Address: net.DomainAddress(domain),
		Port:    0,
	})
	if err != nil {
		return nil, err
	}

	w := &BridgeWorker{
		dispatcher: d,
		tag:        tag,
	}

	// Initialize the connection by sending a Keepalive frame
	keepalive := buf.New()
	mux.FrameMetadata{SessionStatus: mux.SessionStatusKeepAlive}.WriteTo(keepalive)
	err = link.Writer.WriteMultiBuffer(buf.MultiBuffer{keepalive})
	if err != nil {
		return nil, err
	}
	worker, err := mux.NewServerWorker(context.Background(), w, link)
	if err != nil {
		return nil, err
	}
	w.worker = worker

	return w, nil
}

func (w *BridgeWorker) Type() interface{} {
	return routing.DispatcherType()
}

func (w *BridgeWorker) Start() error {
	return nil
}

func (w *BridgeWorker) Close() error {
	return nil
}

func (w *BridgeWorker) IsActive() bool {
	return w.state == Control_ACTIVE && !w.worker.Closed()
}

func (w *BridgeWorker) Connections() uint32 {
	return w.worker.ActiveConnections()
}

func (w *BridgeWorker) handleInternalConn(link transport.Link) {
	go func() {
		reader := link.Reader
		for {
			mb, err := reader.ReadMultiBuffer()
			if err != nil {
				break
			}
			for _, b := range mb {
				var ctl Control
				if err := proto.Unmarshal(b.Bytes(), &ctl); err != nil {
					newError("failed to parse proto message").Base(err).WriteToLog()
					break
				}
				if ctl.State != w.state {
					w.state = ctl.State
				}
			}
		}
	}()
}

func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) {
	if !isInternalDomain(dest) {
		ctx = session.ContextWithInbound(ctx, &session.Inbound{
			Tag: w.tag,
		})
		return w.dispatcher.Dispatch(ctx, dest)
	}

	opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)}
	uplinkReader, uplinkWriter := pipe.New(opt...)
	downlinkReader, downlinkWriter := pipe.New(opt...)

	w.handleInternalConn(transport.Link{
		Reader: downlinkReader,
		Writer: uplinkWriter,
	})

	return &transport.Link{
		Reader: uplinkReader,
		Writer: downlinkWriter,
	}, nil
}
<|endoftext|>


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

// +build go1.8

package proxy

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime"
	"mime/multipart"
	"net/http"
	"reflect"
	"strings"

	"github.com/google/martian/martianlog"
)

// ForReplaying returns a Proxy configured to replay.
func ForReplaying(filename string, port int) (*Proxy, error) {
	p, err := newProxy(filename)
	if err != nil {
		return nil, err
	}
	calls, initial, err := readLog(filename)
	if err != nil {
		return nil, err
	}
	p.mproxy.SetRoundTripper(replayRoundTripper{
		calls:         calls,
		ignoreHeaders: p.ignoreHeaders,
	})
	p.Initial = initial

	// Debug logging.
	// TODO(jba): factor out from here and ForRecording.
	logger := martianlog.NewLogger()
	logger.SetDecode(true)
	p.mproxy.SetRequestModifier(logger)
	p.mproxy.SetResponseModifier(logger)

	if err := p.start(port); err != nil {
		return nil, err
	}
	return p, nil
}

// A call is an HTTP request and its matching response.
type call struct {
	req     *Request
	reqBody *requestBody // parsed request body
	res     *Response
}

func readLog(filename string) ([]*call, []byte, error) {
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, nil, err
	}
	var lg Log
	if err := json.Unmarshal(bytes, &lg); err != nil {
		return nil, nil, fmt.Errorf("%s: %v", filename, err)
	}
	if lg.Version != LogVersion {
		return nil, nil, fmt.Errorf("httpreplay proxy: read log version %s but current version is %s",
			lg.Version, LogVersion)
	}
	ignoreIDs := map[string]bool{} // IDs of requests to ignore
	callsByID := map[string]*call{}
	var calls []*call
	for _, e := range lg.Entries {
		if ignoreIDs[e.ID] {
			continue
		}
		c, ok := callsByID[e.ID]
		switch {
		case !ok:
			if e.Request == nil {
				return nil, nil, fmt.Errorf("first entry for ID %s does not have a request", e.ID)
			}
			if e.Request.Method == "CONNECT" {
				// Ignore CONNECT methods.
				ignoreIDs[e.ID] = true
			} else {
				reqBody, err := newRequestBodyFromLog(e.Request)
				if err != nil {
					return nil, nil, err
				}
				c := &call{e.Request, reqBody, e.Response}
				calls = append(calls, c)
				callsByID[e.ID] = c
			}
		case e.Request != nil:
			if e.Response != nil {
				return nil, nil, errors.New("HAR entry has both request and response")
			}
			c.req = e.Request
		case e.Response != nil:
			c.res = e.Response
		default:
			return nil, nil, errors.New("HAR entry has neither request nor response")
		}
	}
	for _, c := range calls {
		if c.req == nil || c.res == nil {
			return nil, nil, fmt.Errorf("missing request or response: %+v", c)
		}
	}
	return calls, lg.Initial, nil
}

type replayRoundTripper struct {
	calls         []*call
	ignoreHeaders map[string]bool
}

func (r replayRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	reqBody, err := newRequestBodyFromHTTP(req)
	if err != nil {
		return nil, err
	}
	for i, call := range r.calls {
		if call == nil {
			continue
		}
		if requestsMatch(req, reqBody, call.req, call.reqBody, r.ignoreHeaders) {
			r.calls[i] = nil // nil out this call so we don't reuse it
			return toHTTPResponse(call.res, req), nil
		}
	}
	return nil, fmt.Errorf("no matching request for %+v", req)
}

// Headers that shouldn't be compared, because they may differ on different executions
// of the same code, or may not be present during record or replay.
var ignoreHeaders = map[string]bool{}

func init() {
	// Sensitive headers are redacted in the log, so they won't be equal to incoming values.
	for h := range sensitiveHeaders {
		ignoreHeaders[h] = true
	}
	for _, h := range []string{
		"Content-Type", // handled by requestBody
		"Date",
		"Host",
		"Transfer-Encoding",
		"Via",
		"X-Forwarded-For",
		"X-Forwarded-Host",
		"X-Forwarded-Proto",
		"X-Forwarded-Url",
		"X-Cloud-Trace-Context", // OpenCensus traces have a random ID
	} {
		ignoreHeaders[h] = true
	}
}

// Report whether the incoming request in matches the candidate request cand.
func requestsMatch(in *http.Request, inBody *requestBody, cand *Request, candBody *requestBody, ignoreHeaders map[string]bool) bool {
	if in.Method != cand.Method {
		return false
	}
	if in.URL.String() != cand.URL {
		return false
	}
	if !inBody.equal(candBody) {
		return false
	}
	// Check headers last. See DebugHeaders.
	return headersMatch(in.Header, cand.Header, ignoreHeaders)
}

// A requestBody represents the body of a request. If the content type is multipart, the
// body is split into parts.
//
// The replaying proxy needs to understand multipart bodies because the boundaries are
// generated randomly, so we can't just compare the entire bodies for equality.
type requestBody struct {
	mediaType string   // the media type part of the Content-Type header
	parts     [][]byte // the parts of the body, or just a single []byte if not multipart
}

func newRequestBodyFromHTTP(req *http.Request) (*requestBody, error) {
	defer req.Body.Close()
	return newRequestBody(req.Header.Get("Content-Type"), req.Body)
}

func newRequestBodyFromLog(req *Request) (*requestBody, error) {
	if req.Body == nil {
		return nil, nil
	}
	return newRequestBody(req.Header.Get("Content-Type"), bytes.NewReader(req.Body))
}

// newRequestBody parses the Content-Type header, reads the body, and splits it into
// parts if necessary.
func newRequestBody(contentType string, body io.Reader) (*requestBody, error) {
	if contentType == "" {
		// No content-type header. There should not be a body.
		if _, err := body.Read(make([]byte, 1)); err != io.EOF {
			return nil, errors.New("no Content-Type, but body")
		}
		return nil, nil
	}
	mediaType, params, err := mime.ParseMediaType(contentType)
	if err != nil {
		return nil, err
	}
	rb := &requestBody{mediaType: mediaType}
	if strings.HasPrefix(mediaType, "multipart/") {
		mr := multipart.NewReader(body, params["boundary"])
		for {
			p, err := mr.NextPart()
			if err == io.EOF {
				break
			}
			if err != nil {
				return nil, err
			}
			part, err := ioutil.ReadAll(p)
			if err != nil {
				return nil, err
			}
			// TODO(jba): care about part headers?
			rb.parts = append(rb.parts, part)
		}
	} else {
		bytes, err := ioutil.ReadAll(body)
		if err != nil {
			return nil, err
		}
		rb.parts = [][]byte{bytes}
	}
	return rb, nil
}

func (r1 *requestBody) equal(r2 *requestBody) bool {
	if r1 == nil || r2 == nil {
		return r1 == r2
	}
	if r1.mediaType != r2.mediaType {
		return false
	}
	if len(r1.parts) != len(r2.parts) {
		return false
	}
	for i, p1 := range r1.parts {
		if !bytes.Equal(p1, r2.parts[i]) {
			return false
		}
	}
	return true
}

// DebugHeaders helps to determine whether a header should be ignored.
// When true, if requests have the same method, URL and body but differ
// in a header, the first mismatched header is logged.
var DebugHeaders = false

func headersMatch(in, cand http.Header, ignores map[string]bool) bool {
	for k1, v1 := range in {
		if ignores[k1] {
			continue
		}
		v2 := cand[k1]
		if v2 == nil {
			if DebugHeaders {
				log.Printf("header %s: present in incoming request but not candidate", k1)
			}
			return false
		}
		if !reflect.DeepEqual(v1, v2) {
			if DebugHeaders {
				log.Printf("header %s: incoming %v, candidate %v", k1, v1, v2)
			}
			return false
		}
	}
	for k2 := range cand {
		if ignores[k2] {
			continue
		}
		if in[k2] == nil {
			if DebugHeaders {
				log.Printf("header %s: not in incoming request but present in candidate", k2)
			}
			return false
		}
	}
	return true
}
<commit_msg>httpreplay: ignore X-Goog-Api-Client header on replay<commit_after>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build go1.8

package proxy

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime"
	"mime/multipart"
	"net/http"
	"reflect"
	"strings"

	"github.com/google/martian/martianlog"
)

// ForReplaying returns a Proxy configured to replay.
func ForReplaying(filename string, port int) (*Proxy, error) {
	p, err := newProxy(filename)
	if err != nil {
		return nil, err
	}
	calls, initial, err := readLog(filename)
	if err != nil {
		return nil, err
	}
	p.mproxy.SetRoundTripper(replayRoundTripper{
		calls:         calls,
		ignoreHeaders: p.ignoreHeaders,
	})
	p.Initial = initial

	// Debug logging.
	// TODO(jba): factor out from here and ForRecording.
	logger := martianlog.NewLogger()
	logger.SetDecode(true)
	p.mproxy.SetRequestModifier(logger)
	p.mproxy.SetResponseModifier(logger)

	if err := p.start(port); err != nil {
		return nil, err
	}
	return p, nil
}

// A call is an HTTP request and its matching response.
type call struct {
	req     *Request
	reqBody *requestBody // parsed request body
	res     *Response
}

func readLog(filename string) ([]*call, []byte, error) {
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, nil, err
	}
	var lg Log
	if err := json.Unmarshal(bytes, &lg); err != nil {
		return nil, nil, fmt.Errorf("%s: %v", filename, err)
	}
	if lg.Version != LogVersion {
		return nil, nil, fmt.Errorf("httpreplay proxy: read log version %s but current version is %s",
			lg.Version, LogVersion)
	}
	ignoreIDs := map[string]bool{} // IDs of requests to ignore
	callsByID := map[string]*call{}
	var calls []*call
	for _, e := range lg.Entries {
		if ignoreIDs[e.ID] {
			continue
		}
		c, ok := callsByID[e.ID]
		switch {
		case !ok:
			if e.Request == nil {
				return nil, nil, fmt.Errorf("first entry for ID %s does not have a request", e.ID)
			}
			if e.Request.Method == "CONNECT" {
				// Ignore CONNECT methods.
				ignoreIDs[e.ID] = true
			} else {
				reqBody, err := newRequestBodyFromLog(e.Request)
				if err != nil {
					return nil, nil, err
				}
				c := &call{e.Request, reqBody, e.Response}
				calls = append(calls, c)
				callsByID[e.ID] = c
			}
		case e.Request != nil:
			if e.Response != nil {
				return nil, nil, errors.New("HAR entry has both request and response")
			}
			c.req = e.Request
		case e.Response != nil:
			c.res = e.Response
		default:
			return nil, nil, errors.New("HAR entry has neither request nor response")
		}
	}
	for _, c := range calls {
		if c.req == nil || c.res == nil {
			return nil, nil, fmt.Errorf("missing request or response: %+v", c)
		}
	}
	return calls, lg.Initial, nil
}

type replayRoundTripper struct {
	calls         []*call
	ignoreHeaders map[string]bool
}

func (r replayRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	reqBody, err := newRequestBodyFromHTTP(req)
	if err != nil {
		return nil, err
	}
	for i, call := range r.calls {
		if call == nil {
			continue
		}
		if requestsMatch(req, reqBody, call.req, call.reqBody, r.ignoreHeaders) {
			r.calls[i] = nil // nil out this call so we don't reuse it
			return toHTTPResponse(call.res, req), nil
		}
	}
	return nil, fmt.Errorf("no matching request for %+v", req)
}

// Headers that shouldn't be compared, because they may differ on different executions
// of the same code, or may not be present during record or replay.
var ignoreHeaders = map[string]bool{}

func init() {
	// Sensitive headers are redacted in the log, so they won't be equal to incoming values.
	for h := range sensitiveHeaders {
		ignoreHeaders[h] = true
	}
	for _, h := range []string{
		"Content-Type", // handled by requestBody
		"Date",
		"Host",
		"Transfer-Encoding",
		"Via",
		"X-Forwarded-For",
		"X-Forwarded-Host",
		"X-Forwarded-Proto",
		"X-Forwarded-Url",
		"X-Cloud-Trace-Context", // OpenCensus traces have a random ID
		"X-Goog-Api-Client",     // can differ for, e.g., different Go versions
	} {
		ignoreHeaders[h] = true
	}
}

// Report whether the incoming request in matches the candidate request cand.
func requestsMatch(in *http.Request, inBody *requestBody, cand *Request, candBody *requestBody, ignoreHeaders map[string]bool) bool {
	if in.Method != cand.Method {
		return false
	}
	if in.URL.String() != cand.URL {
		return false
	}
	if !inBody.equal(candBody) {
		return false
	}
	// Check headers last. See DebugHeaders.
	return headersMatch(in.Header, cand.Header, ignoreHeaders)
}

// A requestBody represents the body of a request. If the content type is multipart, the
// body is split into parts.
//
// The replaying proxy needs to understand multipart bodies because the boundaries are
// generated randomly, so we can't just compare the entire bodies for equality.
type requestBody struct {
	mediaType string   // the media type part of the Content-Type header
	parts     [][]byte // the parts of the body, or just a single []byte if not multipart
}

func newRequestBodyFromHTTP(req *http.Request) (*requestBody, error) {
	defer req.Body.Close()
	return newRequestBody(req.Header.Get("Content-Type"), req.Body)
}

func newRequestBodyFromLog(req *Request) (*requestBody, error) {
	if req.Body == nil {
		return nil, nil
	}
	return newRequestBody(req.Header.Get("Content-Type"), bytes.NewReader(req.Body))
}

// newRequestBody parses the Content-Type header, reads the body, and splits it into
// parts if necessary.
func newRequestBody(contentType string, body io.Reader) (*requestBody, error) {
	if contentType == "" {
		// No content-type header. There should not be a body.
		if _, err := body.Read(make([]byte, 1)); err != io.EOF {
			return nil, errors.New("no Content-Type, but body")
		}
		return nil, nil
	}
	mediaType, params, err := mime.ParseMediaType(contentType)
	if err != nil {
		return nil, err
	}
	rb := &requestBody{mediaType: mediaType}
	if strings.HasPrefix(mediaType, "multipart/") {
		mr := multipart.NewReader(body, params["boundary"])
		for {
			p, err := mr.NextPart()
			if err == io.EOF {
				break
			}
			if err != nil {
				return nil, err
			}
			part, err := ioutil.ReadAll(p)
			if err != nil {
				return nil, err
			}
			// TODO(jba): care about part headers?
			rb.parts = append(rb.parts, part)
		}
	} else {
		bytes, err := ioutil.ReadAll(body)
		if err != nil {
			return nil, err
		}
		rb.parts = [][]byte{bytes}
	}
	return rb, nil
}

func (r1 *requestBody) equal(r2 *requestBody) bool {
	if r1 == nil || r2 == nil {
		return r1 == r2
	}
	if r1.mediaType != r2.mediaType {
		return false
	}
	if len(r1.parts) != len(r2.parts) {
		return false
	}
	for i, p1 := range r1.parts {
		if !bytes.Equal(p1, r2.parts[i]) {
			return false
		}
	}
	return true
}

// DebugHeaders helps to determine whether a header should be ignored.
// When true, if requests have the same method, URL and body but differ
// in a header, the first mismatched header is logged.
var DebugHeaders = false

func headersMatch(in, cand http.Header, ignores map[string]bool) bool {
	for k1, v1 := range in {
		if ignores[k1] {
			continue
		}
		v2 := cand[k1]
		if v2 == nil {
			if DebugHeaders {
				log.Printf("header %s: present in incoming request but not candidate", k1)
			}
			return false
		}
		if !reflect.DeepEqual(v1, v2) {
			if DebugHeaders {
				log.Printf("header %s: incoming %v, candidate %v", k1, v1, v2)
			}
			return false
		}
	}
	for k2 := range cand {
		if ignores[k2] {
			continue
		}
		if in[k2] == nil {
			if DebugHeaders {
				log.Printf("header %s: not in incoming request but present in candidate", k2)
			}
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package marketTypes

import (
	"time"

	"github.com/antihax/goesi"
	"github.com/sirupsen/logrus"
)

var esiClient *goesi.APIClient
var esiSemaphore = make(chan struct{}, 200)
var typeIDs []int64

// Initialize initializes the market type updates
func Initialize(client *goesi.APIClient) {
	esiClient = client

	updateTypes()
	go scheduleTypeUpdate()
}

// GetMarketTypes returns all typeIDs with a market
func GetMarketTypes() []int64 {
	return typeIDs
}

// Keep ticking in own goroutine and spawn worker tasks.
func scheduleTypeUpdate() {
	ticker := time.NewTicker(2 * time.Hour)
	defer ticker.Stop()
	for {
		<-ticker.C
		go updateTypes()
	}
}

// Update type list
func updateTypes() {
	logrus.Debug("Updating market types.")

	types, err := getMarketTypes()
	if err != nil {
		logrus.WithError(err).Error("Failed to get market types!")
	} else {
		typeIDs = types
	}

	logrus.Debug("Market type update done.")
}

// Get all types on market
func getMarketTypes() ([]int64, error) {
	typeIDs, err := getTypeIDs()
	if err != nil {
		return nil, err
	}

	marketTypes := make(chan int64)
	nonMarketTypes := make(chan int64)
	failure := make(chan error)

	typesLeft := len(typeIDs)

	for _, id := range typeIDs {
		go checkIfMarketTypeAsyncRetry(id, marketTypes, nonMarketTypes, failure)
	}

	var marketTypeIDs []int64

	for typesLeft > 0 {
		select {
		case typeID := <-marketTypes:
			marketTypeIDs = append(marketTypeIDs, typeID)
		case <-nonMarketTypes:
		case err := <-failure:
			logrus.Warnf("Error fetching type from ESI: %s", err.Error())
		}

		typesLeft--
	}

	return marketTypeIDs, nil
}

// Get all typeIDs from ESI
// TODO: move to static-data RPC
func getTypeIDs() ([]int32, error) {
	var typeIDs []int32
	params := make(map[string]interface{})
	params["page"] = int32(1)

	typeResult, _, err := esiClient.ESI.UniverseApi.GetUniverseTypes(nil, params)
	if err != nil {
		return nil, err
	}

	typeIDs = append(typeIDs, typeResult...)

	for len(typeResult) > 0 {
		params["page"] = params["page"].(int32) + 1
		typeResult, _, err = esiClient.ESI.UniverseApi.GetUniverseTypes(nil, params)
		if err != nil {
			return nil, err
		}

		typeIDs = append(typeIDs, typeResult...)
	}

	return typeIDs, nil
}

// Async check if market type, retry 3 times
func checkIfMarketTypeAsyncRetry(typeID int32, marketTypes chan int64, nonMarketTypes chan int64, failure chan error) {
	var isMarketType bool
	var err error
	retries := 3

	for retries > 0 {
		isMarketType, err = checkIfMarketType(typeID)
		if err != nil {
			retries--
		} else {
			err = nil
			retries = 0
		}
	}

	if err != nil {
		failure <- err
		return
	}

	if isMarketType {
		marketTypes <- int64(typeID)
		return
	}

	nonMarketTypes <- int64(typeID)
}

// Check if type is market type
func checkIfMarketType(typeID int32) (bool, error) {
	esiSemaphore <- struct{}{}
	typeInfo, _, err := esiClient.ESI.UniverseApi.GetUniverseTypesTypeId(nil, typeID, nil)
	<-esiSemaphore
	if err != nil {
		return false, err
	}

	// TODO: Once deployed, check for market type again!
	// If it is published and has a market group it is a market type!
	if typeInfo.Published /*&& (typeInfo.MarketGroupId != 0)*/ {
		return true, nil
	}

	return false, nil
}
<commit_msg>Re-enabled market-type checks<commit_after>package marketTypes

import (
	"time"

	"github.com/antihax/goesi"
	"github.com/sirupsen/logrus"
)

var esiClient *goesi.APIClient
var esiSemaphore = make(chan struct{}, 200)
var typeIDs []int64

// Initialize initializes the market type updates
func Initialize(client *goesi.APIClient) {
	esiClient = client

	updateTypes()
	go scheduleTypeUpdate()
}

// GetMarketTypes returns all typeIDs with a market
func GetMarketTypes() []int64 {
	return typeIDs
}

// Keep ticking in own goroutine and spawn worker tasks.
func scheduleTypeUpdate() {
	ticker := time.NewTicker(2 * time.Hour)
	defer ticker.Stop()
	for {
		<-ticker.C
		go updateTypes()
	}
}

// Update type list
func updateTypes() {
	logrus.Debug("Updating market types.")

	types, err := getMarketTypes()
	if err != nil {
		logrus.WithError(err).Error("Failed to get market types!")
	} else {
		typeIDs = types
	}

	logrus.Debug("Market type update done.")
}

// Get all types on market
func getMarketTypes() ([]int64, error) {
	typeIDs, err := getTypeIDs()
	if err != nil {
		return nil, err
	}

	marketTypes := make(chan int64)
	nonMarketTypes := make(chan int64)
	failure := make(chan error)

	typesLeft := len(typeIDs)

	for _, id := range typeIDs {
		go checkIfMarketTypeAsyncRetry(id, marketTypes, nonMarketTypes, failure)
	}

	var marketTypeIDs []int64

	for typesLeft > 0 {
		select {
		case typeID := <-marketTypes:
			marketTypeIDs = append(marketTypeIDs, typeID)
		case <-nonMarketTypes:
		case err := <-failure:
			logrus.Warnf("Error fetching type from ESI: %s", err.Error())
		}

		typesLeft--
	}

	return marketTypeIDs, nil
}

// Get all typeIDs from ESI
// TODO: move to static-data RPC
func getTypeIDs() ([]int32, error) {
	var typeIDs []int32
	params := make(map[string]interface{})
	params["page"] = int32(1)

	typeResult, _, err := esiClient.ESI.UniverseApi.GetUniverseTypes(nil, params)
	if err != nil {
		return nil, err
	}

	typeIDs = append(typeIDs, typeResult...)

	for len(typeResult) > 0 {
		params["page"] = params["page"].(int32) + 1
		typeResult, _, err = esiClient.ESI.UniverseApi.GetUniverseTypes(nil, params)
		if err != nil {
			return nil, err
		}

		typeIDs = append(typeIDs, typeResult...)
	}

	return typeIDs, nil
}

// Async check if market type, retry 3 times
func checkIfMarketTypeAsyncRetry(typeID int32, marketTypes chan int64, nonMarketTypes chan int64, failure chan error) {
	var isMarketType bool
	var err error
	retries := 3

	for retries > 0 {
		isMarketType, err = checkIfMarketType(typeID)
		if err != nil {
			retries--
		} else {
			err = nil
			retries = 0
		}
	}

	if err != nil {
		failure <- err
		return
	}

	if isMarketType {
		marketTypes <- int64(typeID)
		return
	}

	nonMarketTypes <- int64(typeID)
}

// Check if type is market type
func checkIfMarketType(typeID int32) (bool, error) {
	esiSemaphore <- struct{}{}
	typeInfo, _, err := esiClient.ESI.UniverseApi.GetUniverseTypesTypeId(nil, typeID, nil)
	<-esiSemaphore
	if err != nil {
		return false, err
	}

	// If it is published and has a market group it is a market type!
	if typeInfo.Published && (typeInfo.MarketGroupId != 0) {
		return true, nil
	}

	return false, nil
}
<|endoftext|>


<commit_before>package hooksink

import "log"
import "testing"
import "encoding/json"
import "net/http"

import . "github.com/onsi/gomega"

import tu "github.com/xogeny/go-hooksink/testutils"

var samplePush = `
{
  "ref": "refs/heads/gh-pages",
  "after": "4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
  "before": "993b46bdfc03ae59434816829162829e67c4d490",
  "created": false,
  "deleted": false,
  "forced": false,
  "compare": "https://github.com/baxterthehacker/public-repo/compare/993b46bdfc03...4d2ab4e76d0d",
  "commits": [
    {
      "id": "4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
      "distinct": true,
      "message": "Trigger pages build",
      "timestamp": "2014-07-25T12:37:40-04:00",
      "url": "https://github.com/baxterthehacker/public-repo/commit/4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
      "author": {
        "name": "Kyle Daigle",
        "email": "kyle.daigle@github.com",
        "username": "kdaigle"
      },
      "committer": {
        "name": "Kyle Daigle",
        "email": "kyle.daigle@github.com",
        "username": "kdaigle"
      },
      "added": [

      ],
      "removed": [

      ],
      "modified": [
        "index.html"
      ]
    }
  ],
  "head_commit": {
    "id": "4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
    "distinct": true,
    "message": "Trigger pages build",
    "timestamp": "2014-07-25T12:37:40-04:00",
    "url": "https://github.com/baxterthehacker/public-repo/commit/4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
    "author": {
      "name": "Kyle Daigle",
      "email": "kyle.daigle@github.com",
      "username": "kdaigle"
    },
    "committer": {
      "name": "Kyle Daigle",
      "email": "kyle.daigle@github.com",
      "username": "kdaigle"
    },
    "added": [

    ],
    "removed": [

    ],
    "modified": [
      "index.html"
    ]
  },
  "repository": {
    "id": 20000106,
    "name": "public-repo",
    "full_name": "baxterthehacker/public-repo",
    "owner": {
      "name": "baxterthehacker",
      "email": "baxterthehacker@users.noreply.github.com"
    },
    "private": false,
    "html_url": "https://github.com/baxterthehacker/public-repo",
    "description": "",
    "fork": false,
    "url": "https://github.com/baxterthehacker/public-repo",
    "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks",
    "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}",
    "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}",
    "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams",
    "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks",
    "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}",
    "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events",
    "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}",
    "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}",
    "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags",
    "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}",
    "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}",
    "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}",
    "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}",
    "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}",
    "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages",
    "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers",
    "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors",
    "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers",
    "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription",
    "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}",
    "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}",
    "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}",
    "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments/{number}",
    "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}",
    "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}",
    "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges",
    "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}",
    "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads",
    "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}",
    "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}",
    "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}",
    "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}",
    "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}",
    "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}",
    "created_at": 1400625583,
    "updated_at": "2014-07-01T17:21:25Z",
    "pushed_at": 1406306262,
    "git_url": "git://github.com/baxterthehacker/public-repo.git",
    "ssh_url": "git@github.com:baxterthehacker/public-repo.git",
    "clone_url": "https://github.com/baxterthehacker/public-repo.git",
    "svn_url": "https://github.com/baxterthehacker/public-repo",
    "homepage": null,
    "size": 612,
    "stargazers_count": 0,
    "watchers_count": 0,
    "language": null,
    "has_issues": true,
    "has_downloads": true,
    "has_wiki": true,
    "forks_count": 0,
    "mirror_url": null,
    "open_issues_count": 25,
    "forks": 0,
    "open_issues": 25,
    "watchers": 0,
    "default_branch": "master",
    "stargazers": 0,
    "master_branch": "master"
  },
  "pusher": {
    "name": "baxterthehacker",
    "email": "baxterthehacker@users.noreply.github.com"
  }
}
`

type PushChecker struct {
	Called bool;
}

func (pc *PushChecker) Push(msg HubMessage, params map[string][]string) {
	pc.Called = true;
	log.Printf("Parameters = %v", params);
	Expect(msg.Repository.Owner.Name).To(Equal("baxterthehacker"));
}

func TestUnmarshal(t *testing.T) {
	RegisterTestingT(t);

	msg := HubMessage{};
	err := json.Unmarshal([]byte(samplePush), &msg);
	if (err!=nil) {
		log.Print(err.Error());
	}
	Expect(err).To(BeNil());
}

func TestHS1(t *testing.T) {
	RegisterTestingT(t);

	h := NewHookSink("");

	pc := PushChecker{};
	h.Add("/push", &pc);

	status, err := tu.Post(h.Handle, "/push?foo=bar", samplePush);
	Expect(err).To(BeNil());
	Expect(status).To(Equal(200));

	Eventually(func() bool { return pc.Called; }).Should(Equal(true));
}

func TestHS2(t *testing.T) {
	RegisterTestingT(t);

	h := NewHookSink("");

	h.Authenticate(func(req *http.Request) bool {
		return false;
	});

	pc := PushChecker{};
	h.Add("/push", &pc);

	status, err := tu.Post(h.Handle, "/push", samplePush);
	Expect(err).To(BeNil());
	Expect(status).To(Equal(401));
}

func TestHS3(t *testing.T) {
	RegisterTestingT(t);

	secret := "abcdefg";
	h := NewHookSink(secret);

	pc := PushChecker{};
	h.Add("/push", &pc);

	status, err := tu.AuthPost(h.Handle, "/push", samplePush, secret);
	Expect(err).To(BeNil());
	Expect(status).To(Equal(200));

	Eventually(func() bool { return pc.Called; }).Should(Equal(true));
}
<commit_msg>Check repeated parameters<commit_after>package hooksink

import "log"
import "testing"
import "encoding/json"
import "net/http"

import . "github.com/onsi/gomega"

import tu "github.com/xogeny/go-hooksink/testutils"

var samplePush = `
{
  "ref": "refs/heads/gh-pages",
  "after": "4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
  "before": "993b46bdfc03ae59434816829162829e67c4d490",
  "created": false,
  "deleted": false,
  "forced": false,
  "compare": "https://github.com/baxterthehacker/public-repo/compare/993b46bdfc03...4d2ab4e76d0d",
  "commits": [
    {
      "id": "4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
      "distinct": true,
      "message": "Trigger pages build",
      "timestamp": "2014-07-25T12:37:40-04:00",
      "url": "https://github.com/baxterthehacker/public-repo/commit/4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
      "author": {
        "name": "Kyle Daigle",
        "email": "kyle.daigle@github.com",
        "username": "kdaigle"
      },
      "committer": {
        "name": "Kyle Daigle",
        "email": "kyle.daigle@github.com",
        "username": "kdaigle"
      },
      "added": [

      ],
      "removed": [

      ],
      "modified": [
        "index.html"
      ]
    }
  ],
  "head_commit": {
    "id": "4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
    "distinct": true,
    "message": "Trigger pages build",
    "timestamp": "2014-07-25T12:37:40-04:00",
    "url": "https://github.com/baxterthehacker/public-repo/commit/4d2ab4e76d0d405d17d1a0f2b8a6071394e3ab40",
    "author": {
      "name": "Kyle Daigle",
      "email": "kyle.daigle@github.com",
      "username": "kdaigle"
    },
    "committer": {
      "name": "Kyle Daigle",
      "email": "kyle.daigle@github.com",
      "username": "kdaigle"
    },
    "added": [

    ],
    "removed": [

    ],
    "modified": [
      "index.html"
    ]
  },
  "repository": {
    "id": 20000106,
    "name": "public-repo",
    "full_name": "baxterthehacker/public-repo",
    "owner": {
      "name": "baxterthehacker",
      "email": "baxterthehacker@users.noreply.github.com"
    },
    "private": false,
    "html_url": "https://github.com/baxterthehacker/public-repo",
    "description": "",
    "fork": false,
    "url": "https://github.com/baxterthehacker/public-repo",
    "forks_url": "https://api.github.com/repos/baxterthehacker/public-repo/forks",
    "keys_url": "https://api.github.com/repos/baxterthehacker/public-repo/keys{/key_id}",
    "collaborators_url": "https://api.github.com/repos/baxterthehacker/public-repo/collaborators{/collaborator}",
    "teams_url": "https://api.github.com/repos/baxterthehacker/public-repo/teams",
    "hooks_url": "https://api.github.com/repos/baxterthehacker/public-repo/hooks",
    "issue_events_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/events{/number}",
    "events_url": "https://api.github.com/repos/baxterthehacker/public-repo/events",
    "assignees_url": "https://api.github.com/repos/baxterthehacker/public-repo/assignees{/user}",
    "branches_url": "https://api.github.com/repos/baxterthehacker/public-repo/branches{/branch}",
    "tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/tags",
    "blobs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/blobs{/sha}",
    "git_tags_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/tags{/sha}",
    "git_refs_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/refs{/sha}",
    "trees_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/trees{/sha}",
    "statuses_url": "https://api.github.com/repos/baxterthehacker/public-repo/statuses/{sha}",
    "languages_url": "https://api.github.com/repos/baxterthehacker/public-repo/languages",
    "stargazers_url": "https://api.github.com/repos/baxterthehacker/public-repo/stargazers",
    "contributors_url": "https://api.github.com/repos/baxterthehacker/public-repo/contributors",
    "subscribers_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscribers",
    "subscription_url": "https://api.github.com/repos/baxterthehacker/public-repo/subscription",
    "commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/commits{/sha}",
    "git_commits_url": "https://api.github.com/repos/baxterthehacker/public-repo/git/commits{/sha}",
    "comments_url": "https://api.github.com/repos/baxterthehacker/public-repo/comments{/number}",
    "issue_comment_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues/comments/{number}",
    "contents_url": "https://api.github.com/repos/baxterthehacker/public-repo/contents/{+path}",
    "compare_url": "https://api.github.com/repos/baxterthehacker/public-repo/compare/{base}...{head}",
    "merges_url": "https://api.github.com/repos/baxterthehacker/public-repo/merges",
    "archive_url": "https://api.github.com/repos/baxterthehacker/public-repo/{archive_format}{/ref}",
    "downloads_url": "https://api.github.com/repos/baxterthehacker/public-repo/downloads",
    "issues_url": "https://api.github.com/repos/baxterthehacker/public-repo/issues{/number}",
    "pulls_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls{/number}",
    "milestones_url": "https://api.github.com/repos/baxterthehacker/public-repo/milestones{/number}",
    "notifications_url": "https://api.github.com/repos/baxterthehacker/public-repo/notifications{?since,all,participating}",
    "labels_url": "https://api.github.com/repos/baxterthehacker/public-repo/labels{/name}",
    "releases_url": "https://api.github.com/repos/baxterthehacker/public-repo/releases{/id}",
    "created_at": 1400625583,
    "updated_at": "2014-07-01T17:21:25Z",
    "pushed_at": 1406306262,
    "git_url": "git://github.com/baxterthehacker/public-repo.git",
    "ssh_url": "git@github.com:baxterthehacker/public-repo.git",
    "clone_url": "https://github.com/baxterthehacker/public-repo.git",
    "svn_url": "https://github.com/baxterthehacker/public-repo",
    "homepage": null,
    "size": 612,
    "stargazers_count": 0,
    "watchers_count": 0,
    "language": null,
    "has_issues": true,
    "has_downloads": true,
    "has_wiki": true,
    "forks_count": 0,
    "mirror_url": null,
    "open_issues_count": 25,
    "forks": 0,
    "open_issues": 25,
    "watchers": 0,
    "default_branch": "master",
    "stargazers": 0,
    "master_branch": "master"
  },
  "pusher": {
    "name": "baxterthehacker",
    "email": "baxterthehacker@users.noreply.github.com"
  }
}
`

type PushChecker struct {
	Called bool;
}

func (pc *PushChecker) Push(msg HubMessage, params map[string][]string) {
	pc.Called = true;
	log.Printf("Parameters = %v", params);
	Expect(msg.Repository.Owner.Name).To(Equal("baxterthehacker"));
}

func TestUnmarshal(t *testing.T) {
	RegisterTestingT(t);

	msg := HubMessage{};
	err := json.Unmarshal([]byte(samplePush), &msg);
	if (err!=nil) {
		log.Print(err.Error());
	}
	Expect(err).To(BeNil());
}

func TestHS1(t *testing.T) {
	RegisterTestingT(t);

	h := NewHookSink("");

	pc := PushChecker{};
	h.Add("/push", &pc);

	status, err := tu.Post(h.Handle, "/push?foo=bar&foo=buz", samplePush);
	Expect(err).To(BeNil());
	Expect(status).To(Equal(200));

	Eventually(func() bool { return pc.Called; }).Should(Equal(true));
}

func TestHS2(t *testing.T) {
	RegisterTestingT(t);

	h := NewHookSink("");

	h.Authenticate(func(req *http.Request) bool {
		return false;
	});

	pc := PushChecker{};
	h.Add("/push", &pc);

	status, err := tu.Post(h.Handle, "/push", samplePush);
	Expect(err).To(BeNil());
	Expect(status).To(Equal(401));
}

func TestHS3(t *testing.T) {
	RegisterTestingT(t);

	secret := "abcdefg";
	h := NewHookSink(secret);

	pc := PushChecker{};
	h.Add("/push", &pc);

	status, err := tu.AuthPost(h.Handle, "/push", samplePush, secret);
	Expect(err).To(BeNil());
	Expect(status).To(Equal(200));

	Eventually(func() bool { return pc.Called; }).Should(Equal(true));
}
<|endoftext|>


<commit_before>package blueprint

import (
	"fmt"
	"path/filepath"
)

type Singleton interface {
	GenerateBuildActions(SingletonContext)
}

type SingletonContext interface {
	Config() interface{}

	ModuleName(module Module) string
	ModuleDir(module Module) string
	BlueprintFile(module Module) string

	ModuleErrorf(module Module, format string, args ...interface{})
	Errorf(format string, args ...interface{})

	Variable(name, value string)
	Rule(name string, params RuleParams, argNames ...string) Rule
	Build(params BuildParams)
	RequireNinjaVersion(major, minor, micro int)

	// SetBuildDir sets the value of the top-level "builddir" Ninja variable
	// that controls where Ninja stores its build log files.  This value can be
	// set at most one time for a single build.  Setting it multiple times (even
	// across different singletons) will result in a panic.
	SetBuildDir(value string)

	VisitAllModules(visit func(Module))
	VisitAllModulesIf(pred func(Module) bool, visit func(Module))
}

var _ SingletonContext = (*singletonContext)(nil)

type singletonContext struct {
	context *Context
	config  interface{}
	scope   *localScope

	errs []error

	actionDefs localBuildActions
}

func (s *singletonContext) Config() interface{} {
	return s.config
}

func (s *singletonContext) ModuleName(module Module) string {
	info := s.context.moduleInfo[module]
	return info.properties.Name
}

func (s *singletonContext) ModuleDir(module Module) string {
	info := s.context.moduleInfo[module]
	return filepath.Dir(info.relBlueprintsFile)
}

func (s *singletonContext) BlueprintFile(module Module) string {
	info := s.context.moduleInfo[module]
	return info.relBlueprintsFile
}

func (s *singletonContext) ModuleErrorf(module Module, format string,
	args ...interface{}) {

	info := s.context.moduleInfo[module]
	s.errs = append(s.errs, &Error{
		Err: fmt.Errorf(format, args...),
		Pos: info.pos,
	})
}

func (s *singletonContext) Errorf(format string, args ...interface{}) {
	// TODO: Make this not result in the error being printed as "internal error"
	s.errs = append(s.errs, fmt.Errorf(format, args...))
}

func (s *singletonContext) Variable(name, value string) {
	v, err := s.scope.AddLocalVariable(name, value)
	if err != nil {
		panic(err)
	}

	s.actionDefs.variables = append(s.actionDefs.variables, v)
}

func (s *singletonContext) Rule(name string, params RuleParams,
	argNames ...string) Rule {

	r, err := s.scope.AddLocalRule(name, &params, argNames...)
	if err != nil {
		panic(err)
	}

	s.actionDefs.rules = append(s.actionDefs.rules, r)

	return r
}

func (s *singletonContext) Build(params BuildParams) {
	def, err := parseBuildParams(s.scope, &params)
	if err != nil {
		panic(err)
	}

	s.actionDefs.buildDefs = append(s.actionDefs.buildDefs, def)
}

func (s *singletonContext) RequireNinjaVersion(major, minor, micro int) {
	s.context.requireNinjaVersion(major, minor, micro)
}

func (s *singletonContext) SetBuildDir(value string) {
	ninjaValue, err := parseNinjaString(s.scope, value)
	if err != nil {
		panic(err)
	}

	s.context.setBuildDir(ninjaValue)
}

func (s *singletonContext) VisitAllModules(visit func(Module)) {
	s.context.visitAllModules(visit)
}

func (s *singletonContext) VisitAllModulesIf(pred func(Module) bool,
	visit func(Module)) {

	s.context.visitAllModulesIf(pred, visit)
}
<commit_msg>Add support for singletons to walk module deps.<commit_after>package blueprint

import (
	"fmt"
	"path/filepath"
)

type Singleton interface {
	GenerateBuildActions(SingletonContext)
}

type SingletonContext interface {
	Config() interface{}

	ModuleName(module Module) string
	ModuleDir(module Module) string
	BlueprintFile(module Module) string

	ModuleErrorf(module Module, format string, args ...interface{})
	Errorf(format string, args ...interface{})

	Variable(name, value string)
	Rule(name string, params RuleParams, argNames ...string) Rule
	Build(params BuildParams)
	RequireNinjaVersion(major, minor, micro int)

	// SetBuildDir sets the value of the top-level "builddir" Ninja variable
	// that controls where Ninja stores its build log files.  This value can be
	// set at most one time for a single build.  Setting it multiple times (even
	// across different singletons) will result in a panic.
	SetBuildDir(value string)

	VisitAllModules(visit func(Module))
	VisitAllModulesIf(pred func(Module) bool, visit func(Module))
	VisitDepsDepthFirst(module Module, visit func(Module))
	VisitDepsDepthFirstIf(module Module, pred func(Module) bool,
		visit func(Module))
}

var _ SingletonContext = (*singletonContext)(nil)

type singletonContext struct {
	context *Context
	config  interface{}
	scope   *localScope

	errs []error

	actionDefs localBuildActions
}

func (s *singletonContext) Config() interface{} {
	return s.config
}

func (s *singletonContext) ModuleName(module Module) string {
	info := s.context.moduleInfo[module]
	return info.properties.Name
}

func (s *singletonContext) ModuleDir(module Module) string {
	info := s.context.moduleInfo[module]
	return filepath.Dir(info.relBlueprintsFile)
}

func (s *singletonContext) BlueprintFile(module Module) string {
	info := s.context.moduleInfo[module]
	return info.relBlueprintsFile
}

func (s *singletonContext) ModuleErrorf(module Module, format string,
	args ...interface{}) {

	info := s.context.moduleInfo[module]
	s.errs = append(s.errs, &Error{
		Err: fmt.Errorf(format, args...),
		Pos: info.pos,
	})
}

func (s *singletonContext) Errorf(format string, args ...interface{}) {
	// TODO: Make this not result in the error being printed as "internal error"
	s.errs = append(s.errs, fmt.Errorf(format, args...))
}

func (s *singletonContext) Variable(name, value string) {
	v, err := s.scope.AddLocalVariable(name, value)
	if err != nil {
		panic(err)
	}

	s.actionDefs.variables = append(s.actionDefs.variables, v)
}

func (s *singletonContext) Rule(name string, params RuleParams,
	argNames ...string) Rule {

	r, err := s.scope.AddLocalRule(name, &params, argNames...)
	if err != nil {
		panic(err)
	}

	s.actionDefs.rules = append(s.actionDefs.rules, r)

	return r
}

func (s *singletonContext) Build(params BuildParams) {
	def, err := parseBuildParams(s.scope, &params)
	if err != nil {
		panic(err)
	}

	s.actionDefs.buildDefs = append(s.actionDefs.buildDefs, def)
}

func (s *singletonContext) RequireNinjaVersion(major, minor, micro int) {
	s.context.requireNinjaVersion(major, minor, micro)
}

func (s *singletonContext) SetBuildDir(value string) {
	ninjaValue, err := parseNinjaString(s.scope, value)
	if err != nil {
		panic(err)
	}

	s.context.setBuildDir(ninjaValue)
}

func (s *singletonContext) VisitAllModules(visit func(Module)) {
	s.context.visitAllModules(visit)
}

func (s *singletonContext) VisitAllModulesIf(pred func(Module) bool,
	visit func(Module)) {

	s.context.visitAllModulesIf(pred, visit)
}

func (s *singletonContext) VisitDepsDepthFirst(module Module,
	visit func(Module)) {

	s.context.visitDepsDepthFirst(module, visit)
}

func (s *singletonContext) VisitDepsDepthFirstIf(module Module,
	pred func(Module) bool, visit func(Module)) {

	s.context.visitDepsDepthFirstIf(module, pred, visit)
}
<|endoftext|>


<commit_before>package cdsclient

import (
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"runtime/pprof"
	"strings"
	"time"

	"github.com/gorilla/websocket"

	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

func (c *client) RequestWebsocket(ctx context.Context, path string, msgToSend <-chan sdk.WebsocketFilter, msgReceived chan<- sdk.WebsocketEvent) error {
	wsContext, wsContextCancel := context.WithCancel(ctx)
	defer wsContextCancel()

	// Checks that current session_token is still valid
	// If not, challenge a new one against the authenticationToken
	if !c.config.HasValidSessionToken() && c.config.BuitinConsumerAuthenticationToken != "" {
		resp, err := c.AuthConsumerSignin(sdk.ConsumerBuiltin, sdk.AuthConsumerSigninRequest{"token": c.config.BuitinConsumerAuthenticationToken})
		if err != nil {
			return err
		}
		c.config.SessionToken = resp.Token
	}

	labels := pprof.Labels("path", path, "method", "GET")
	wsContext = pprof.WithLabels(wsContext, labels)
	pprof.SetGoroutineLabels(wsContext)

	uHost, err := url.Parse(c.config.Host)
	if err != nil {
		return sdk.WrapError(err, "wrong Host configuration")
	}
	urlWebsocket := url.URL{
		Scheme: strings.Replace(uHost.Scheme, "http", "ws", -1),
		Host:   uHost.Host,
		Path:   "/ws",
	}

	headers := make(map[string][]string)
	date := sdk.FormatDateRFC5322(time.Now())
	headers["Date"] = []string{date}
	headers["X-CDS-RemoteTime"] = []string{date}
	auth := "Bearer " + c.config.SessionToken
	headers["Authorization"] = []string{auth}
	con, _, err := c.httpWebsocketClient.Dial(urlWebsocket.String(), headers)
	if err != nil {
		return sdk.WithStack(err)
	}
	defer con.Close() // nolint

	// Message to send
	sdk.GoRoutine(wsContext, fmt.Sprintf("RequestWebsocket-%s-%s", c.config.User, sdk.UUID()), func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				log.Warning(wsContext, "Leaving....")
				return
			case m := <-msgToSend:
				if err := con.WriteJSON(m); err != nil {
					log.Error(wsContext, "ws: unable to send message: %v", err)
				}
			}
		}
	})

	for {
		if ctx.Err() != nil {
			return ctx.Err()
		}
		_, message, err := con.ReadMessage()
		if err != nil {
			if websocket.IsCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Warning(ctx, "websocket error: %v", err)
				return err
			}
			log.Error(ctx, "ws: unable to read message: %v", err)
			continue
		}
		var wsEvent sdk.WebsocketEvent
		if err := json.Unmarshal(message, &wsEvent); err != nil {
			log.Error(ctx, "ws: unable to unmarshal message: %s : %v", string(message), err)
			continue
		}
		msgReceived <- wsEvent
	}
}
<commit_msg>fix(sdk): missing host path part on websocket request (#5263)<commit_after>package cdsclient

import (
	"context"
	"encoding/json"
	"fmt"
	"net/url"
	"runtime/pprof"
	"strings"
	"time"

	"github.com/gorilla/websocket"

	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

func (c *client) RequestWebsocket(ctx context.Context, path string, msgToSend <-chan sdk.WebsocketFilter, msgReceived chan<- sdk.WebsocketEvent) error {
	wsContext, wsContextCancel := context.WithCancel(ctx)
	defer wsContextCancel()

	// Checks that current session_token is still valid
	// If not, challenge a new one against the authenticationToken
	if !c.config.HasValidSessionToken() && c.config.BuitinConsumerAuthenticationToken != "" {
		resp, err := c.AuthConsumerSignin(sdk.ConsumerBuiltin, sdk.AuthConsumerSigninRequest{"token": c.config.BuitinConsumerAuthenticationToken})
		if err != nil {
			return err
		}
		c.config.SessionToken = resp.Token
	}

	labels := pprof.Labels("path", path, "method", "GET")
	wsContext = pprof.WithLabels(wsContext, labels)
	pprof.SetGoroutineLabels(wsContext)

	uHost, err := url.Parse(c.config.Host)
	if err != nil {
		return sdk.WrapError(err, "wrong Host configuration")
	}
	urlWebsocket := url.URL{
		Scheme: strings.Replace(uHost.Scheme, "http", "ws", -1),
		Host:   uHost.Host,
		Path:   uHost.Path + "/ws",
	}

	headers := make(map[string][]string)
	date := sdk.FormatDateRFC5322(time.Now())
	headers["Date"] = []string{date}
	headers["X-CDS-RemoteTime"] = []string{date}
	auth := "Bearer " + c.config.SessionToken
	headers["Authorization"] = []string{auth}
	con, _, err := c.httpWebsocketClient.Dial(urlWebsocket.String(), headers)
	if err != nil {
		return sdk.WithStack(err)
	}
	defer con.Close() // nolint

	// Message to send
	sdk.GoRoutine(wsContext, fmt.Sprintf("RequestWebsocket-%s-%s", c.config.User, sdk.UUID()), func(ctx context.Context) {
		for {
			select {
			case <-ctx.Done():
				log.Warning(wsContext, "Leaving....")
				return
			case m := <-msgToSend:
				if err := con.WriteJSON(m); err != nil {
					log.Error(wsContext, "ws: unable to send message: %v", err)
				}
			}
		}
	})

	for {
		if ctx.Err() != nil {
			return ctx.Err()
		}
		_, message, err := con.ReadMessage()
		if err != nil {
			if websocket.IsCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Warning(ctx, "websocket error: %v", err)
				return err
			}
			log.Error(ctx, "ws: unable to read message: %v", err)
			continue
		}
		var wsEvent sdk.WebsocketEvent
		if err := json.Unmarshal(message, &wsEvent); err != nil {
			log.Error(ctx, "ws: unable to unmarshal message: %s : %v", string(message), err)
			continue
		}
		msgReceived <- wsEvent
	}
}
<|endoftext|>


<commit_before>package htmlrefs

import (
	"strings"
	"testing"
)

func TestAll(t *testing.T) {

	// the required resources to be found in the example
	reqs := [...]Reference{
		{URI: "/favicon.ico", Token: "link"},
		{URI: "/foo.css", Token: "link"},
		{URI: "/async.js", Token: "script"},
		{URI: "/foo.js", Token: "script"},
		{URI: "/relative", Token: "a"},
		{URI: "http://example.com/absolute", Token: "a"},
		{URI: "//google.com/protocol-relative", Token: "a"},
		{URI: "#yep", Token: "a"},
		{URI: "/sample.jpg", Token: "img"},
		{URI: "/logo.svg", Token: "source"},
		{URI: "/logo.webp", Token: "source"},
		{URI: "/logo.png", Token: "source"},
		{URI: "/logo.gif", Token: "img"},
		{URI: "/vid.mp4", Token: "video"},
	}

	// example HTML reader
	reader := strings.NewReader(`
		<!DOCTYPE html>

				<title>example</title>
				<link rel="icon" href="/favicon.ico">
				<link rel="stylesheet" type="text/css" href="/foo.css" />

				yep

				<picture>
				   <source type="image/svg+xml" srcset="/logo.svg" />
				   <source type="image/webp" srcset="/logo.webp" />
				   <source type="image/png" srcset="/logo.png" />

				</picture>

nope

				<video src="/vid.mp4"></video>

	`)

	// get the refs from the implementation
	refs := All(reader)

	need, have := len(reqs), len(refs)
	if need != have {
		t.Errorf("Wrong number of refs returned. need: %d , have: %d", need, have)
	}

	for i := 0; i < len(reqs); i++ {
		uri := reqs[i].URI
		ref := refs[i]

		if uri != ref {
			t.Errorf("Mismatch URI detected. need: %s , have: %s", uri, ref)
		}
	}
}
<commit_msg>extending tests suite to check for tokens<commit_after>package htmlrefs

import (
	"strings"
	"testing"
)

func TestAll(t *testing.T) {

	// the required resources to be found in the example
	reqs := [...]Reference{
		{URI: "/favicon.ico", Token: "link"},
		{URI: "/foo.css", Token: "link"},
		{URI: "/async.js", Token: "script"},
		{URI: "/foo.js", Token: "script"},
		{URI: "/relative", Token: "a"},
		{URI: "http://example.com/absolute", Token: "a"},
		{URI: "//google.com/protocol-relative", Token: "a"},
		{URI: "#yep", Token: "a"},
		{URI: "/sample.jpg", Token: "img"},
		{URI: "/logo.svg", Token: "source"},
		{URI: "/logo.webp", Token: "source"},
		{URI: "/logo.png", Token: "source"},
		{URI: "/logo.gif", Token: "img"},
		{URI: "/vid.mp4", Token: "video"},
	}

	// example HTML reader
	reader := strings.NewReader(`
		<!DOCTYPE html>

				<title>example</title>
				<link rel="icon" href="/favicon.ico">
				<link rel="stylesheet" type="text/css" href="/foo.css" />

				yep

				<picture>
				   <source type="image/svg+xml" srcset="/logo.svg" />
				   <source type="image/webp" srcset="/logo.webp" />
				   <source type="image/png" srcset="/logo.png" />

				</picture>

nope

				<video src="/vid.mp4"></video>

	`)

	// get the refs from the implementation
	refs := All(reader)

	need, have := len(reqs), len(refs)
	if need != have {
		t.Errorf("Wrong number of refs returned. need: %d , have: %d", need, have)
	}

	// loop through and verify URI and Token names
	for i, req := range reqs {
		ref := refs[i]

		if req.URI != ref.URI {
			t.Errorf("Mismatch URI detected. need: %s , have: %s", req.URI, ref.URI)
		}

		if req.Token != ref.Token {
			t.Errorf("Mismatch Token detected. need: %s , have: %s", req.Token, ref.Token)
		}
	}
}
<|endoftext|>


<commit_before><commit_msg>alterando os projects para array<commit_after><|endoftext|>


<commit_before>package main

import (
	"bytes"
	"hash/fnv"

	"github.com/buger/gor/proto"
)

type HTTPModifier struct {
	config *HTTPModifierConfig
}

func NewHTTPModifier(config *HTTPModifierConfig) *HTTPModifier {
	// Optimization to skip modifier completely if we do not need it
	if len(config.urlRegexp) == 0 &&
		len(config.urlNegativeRegexp) == 0 &&
		len(config.urlRewrite) == 0 &&
		len(config.headerFilters) == 0 &&
		len(config.headerNegativeFilters) == 0 &&
		len(config.headerHashFilters) == 0 &&
		len(config.paramHashFilters) == 0 &&
		len(config.params) == 0 &&
		len(config.headers) == 0 &&
		len(config.methods) == 0 {
		return nil
	}

	return &HTTPModifier{config: config}
}

func (m *HTTPModifier) Rewrite(payload []byte) (response []byte) {
	if len(m.config.methods) > 0 {
		method := proto.Method(payload)

		matched := false

		for _, m := range m.config.methods {
			if bytes.Equal(method, m) {
				matched = true
				break
			}
		}

		if !matched {
			return
		}
	}

	if len(m.config.headers) > 0 {
		for _, header := range m.config.headers {
			payload = proto.SetHeader(payload, []byte(header.Name), []byte(header.Value))
		}
	}

	if len(m.config.params) > 0 {
		for _, param := range m.config.params {
			payload = proto.SetPathParam(payload, param.Name, param.Value)
		}
	}

	if len(m.config.urlRegexp) > 0 {
		path := proto.Path(payload)

		matched := false

		for _, f := range m.config.urlRegexp {
			if f.regexp.Match(path) {
				matched = true
				break
			}
		}

		if !matched {
			return
		}
	}

	if len(m.config.urlNegativeRegexp) > 0 {
		path := proto.Path(payload)

		for _, f := range m.config.urlNegativeRegexp {
			if f.regexp.Match(path) {
				return
			}
		}
	}

	if len(m.config.headerFilters) > 0 {
		for _, f := range m.config.headerFilters {
			value := proto.Header(payload, f.name)

			if len(value) > 0 && !f.regexp.Match(value) {
				return
			}
		}
	}

	if len(m.config.headerNegativeFilters) > 0 {
		for _, f := range m.config.headerNegativeFilters {
			value, s, _, _ := proto.Header(payload, f.name)

			if s != -1 && f.regexp.Match(value) {
				return
			}
		}
	}

	if len(m.config.headerHashFilters) > 0 {
		for _, f := range m.config.headerHashFilters {
			value := proto.Header(payload, f.name)

			if len(value) > 0 {
				hasher := fnv.New32a()
				hasher.Write(value)

				if (hasher.Sum32() % 100) >= f.percent {
					return
				}
			}
		}
	}

	if len(m.config.paramHashFilters) > 0 {
		for _, f := range m.config.paramHashFilters {
			value, s, _ := proto.PathParam(payload, f.name)

			if s != -1 {
				hasher := fnv.New32a()
				hasher.Write(value)

				if (hasher.Sum32() % 100) >= f.percent {
					return
				}
			}
		}
	}

	if len(m.config.urlRewrite) > 0 {
		path := proto.Path(payload)

		for _, f := range m.config.urlRewrite {
			if f.src.Match(path) {
				path = f.src.ReplaceAll(path, f.target)
				payload = proto.SetPath(payload, path)

				break
			}
		}
	}

	return payload
}
<commit_msg>Fix tests<commit_after>package main

import (
	"bytes"
	"hash/fnv"

	"github.com/buger/gor/proto"
)

type HTTPModifier struct {
	config *HTTPModifierConfig
}

func NewHTTPModifier(config *HTTPModifierConfig) *HTTPModifier {
	// Optimization to skip modifier completely if we do not need it
	if len(config.urlRegexp) == 0 &&
		len(config.urlNegativeRegexp) == 0 &&
		len(config.urlRewrite) == 0 &&
		len(config.headerFilters) == 0 &&
		len(config.headerNegativeFilters) == 0 &&
		len(config.headerHashFilters) == 0 &&
		len(config.paramHashFilters) == 0 &&
		len(config.params) == 0 &&
		len(config.headers) == 0 &&
		len(config.methods) == 0 {
		return nil
	}

	return &HTTPModifier{config: config}
}

func (m *HTTPModifier) Rewrite(payload []byte) (response []byte) {
	if len(m.config.methods) > 0 {
		method := proto.Method(payload)

		matched := false

		for _, m := range m.config.methods {
			if bytes.Equal(method, m) {
				matched = true
				break
			}
		}

		if !matched {
			return
		}
	}

	if len(m.config.headers) > 0 {
		for _, header := range m.config.headers {
			payload = proto.SetHeader(payload, []byte(header.Name), []byte(header.Value))
		}
	}

	if len(m.config.params) > 0 {
		for _, param := range m.config.params {
			payload = proto.SetPathParam(payload, param.Name, param.Value)
		}
	}

	if len(m.config.urlRegexp) > 0 {
		path := proto.Path(payload)

		matched := false

		for _, f := range m.config.urlRegexp {
			if f.regexp.Match(path) {
				matched = true
				break
			}
		}

		if !matched {
			return
		}
	}

	if len(m.config.urlNegativeRegexp) > 0 {
		path := proto.Path(payload)

		for _, f := range m.config.urlNegativeRegexp {
			if f.regexp.Match(path) {
				return
			}
		}
	}

	if len(m.config.headerFilters) > 0 {
		for _, f := range m.config.headerFilters {
			value := proto.Header(payload, f.name)

			if len(value) > 0 && !f.regexp.Match(value) {
				return
			}
		}
	}

	if len(m.config.headerNegativeFilters) > 0 {
		for _, f := range m.config.headerNegativeFilters {
			value := proto.Header(payload, f.name)

			if s != -1 && f.regexp.Match(value) {
				return
			}
		}
	}

	if len(m.config.headerHashFilters) > 0 {
		for _, f := range m.config.headerHashFilters {
			value := proto.Header(payload, f.name)

			if len(value) > 0 {
				hasher := fnv.New32a()
				hasher.Write(value)

				if (hasher.Sum32() % 100) >= f.percent {
					return
				}
			}
		}
	}

	if len(m.config.paramHashFilters) > 0 {
		for _, f := range m.config.paramHashFilters {
			value, s, _ := proto.PathParam(payload, f.name)

			if s != -1 {
				hasher := fnv.New32a()
				hasher.Write(value)

				if (hasher.Sum32() % 100) >= f.percent {
					return
				}
			}
		}
	}

	if len(m.config.urlRewrite) > 0 {
		path := proto.Path(payload)

		for _, f := range m.config.urlRewrite {
			if f.src.Match(path) {
				path = f.src.ReplaceAll(path, f.target)
				payload = proto.SetPath(payload, path)

				break
			}
		}
	}

	return payload
}
<|endoftext|>


<commit_before>package contract

import (
	"path/filepath"
	"strings"

	"github.com/smartystreets/goconvey/convey/reporting"
	"github.com/smartystreets/goconvey/web/server/messaging"
)

type Package struct {
	Path          string
	Name          string
	Ignored       bool
	Disabled      bool
	TestArguments []string
	Error         error
	Output        string
	Result        *PackageResult

	HasImportCycle bool
}

func NewPackage(folder *messaging.Folder, hasImportCycle bool) *Package {
	self := new(Package)
	self.Path = folder.Path
	self.Name = resolvePackageName(self.Path)
	self.Result = NewPackageResult(self.Name)
	self.Ignored = folder.Ignored
	self.Disabled = folder.Disabled
	self.TestArguments = folder.TestArguments
	self.HasImportCycle = hasImportCycle
	return self
}

func (self *Package) Active() bool {
	return !self.Disabled && !self.Ignored
}

func (self *Package) HasUsableResult() bool {
	return self.Active() && (self.Error == nil || (self.Output != ""))
}

type CompleteOutput struct {
	Packages []*PackageResult
	Revision string
	Paused   bool
}

var ( // PackageResult.Outcome values:
	Ignored         = "ignored"
	Disabled        = "disabled"
	Passed          = "passed"
	Failed          = "failed"
	Panicked        = "panicked"
	BuildFailure    = "build failure"
	NoTestFiles     = "no test files"
	NoTestFunctions = "no test functions"
	NoGoFiles       = "no go code"

	TestRunAbortedUnexpectedly = "test run aborted unexpectedly"
)

type PackageResult struct {
	PackageName string
	Elapsed     float64
	Coverage    float64
	Outcome     string
	BuildOutput string
	TestResults []TestResult
}

func NewPackageResult(packageName string) *PackageResult {
	self := new(PackageResult)
	self.PackageName = packageName
	self.TestResults = []TestResult{}
	self.Coverage = -1
	return self
}

type TestResult struct {
	TestName string
	Elapsed  float64
	Passed   bool
	Skipped  bool
	File     string
	Line     int
	Message  string
	Error    string
	Stories  []reporting.ScopeResult

	RawLines []string `json:",omitempty"`
}

func NewTestResult(testName string) *TestResult {
	self := new(TestResult)
	self.Stories = []reporting.ScopeResult{}
	self.RawLines = []string{}
	self.TestName = testName
	return self
}

func resolvePackageName(path string) string {
	nameArr := strings.Split(path, endGoPath)
	return nameArr[len(nameArr)-1]
}

const (
	separator = string(filepath.Separator)
	endGoPath = separator + "src" + separator
)
<commit_msg>Using build.ImportDir to resolve package names where possible. Closes #303 and should close #299 as well.<commit_after>package contract

import (
	"path/filepath"
	"strings"

	"go/build"

	"github.com/smartystreets/goconvey/convey/reporting"
	"github.com/smartystreets/goconvey/web/server/messaging"
)

type Package struct {
	Path          string
	Name          string
	Ignored       bool
	Disabled      bool
	TestArguments []string
	Error         error
	Output        string
	Result        *PackageResult

	HasImportCycle bool
}

func NewPackage(folder *messaging.Folder, hasImportCycle bool) *Package {
	self := new(Package)
	self.Path = folder.Path
	self.Name = resolvePackageName(self.Path)
	self.Result = NewPackageResult(self.Name)
	self.Ignored = folder.Ignored
	self.Disabled = folder.Disabled
	self.TestArguments = folder.TestArguments
	self.HasImportCycle = hasImportCycle
	return self
}

func (self *Package) Active() bool {
	return !self.Disabled && !self.Ignored
}

func (self *Package) HasUsableResult() bool {
	return self.Active() && (self.Error == nil || (self.Output != ""))
}

type CompleteOutput struct {
	Packages []*PackageResult
	Revision string
	Paused   bool
}

var ( // PackageResult.Outcome values:
	Ignored         = "ignored"
	Disabled        = "disabled"
	Passed          = "passed"
	Failed          = "failed"
	Panicked        = "panicked"
	BuildFailure    = "build failure"
	NoTestFiles     = "no test files"
	NoTestFunctions = "no test functions"
	NoGoFiles       = "no go code"

	TestRunAbortedUnexpectedly = "test run aborted unexpectedly"
)

type PackageResult struct {
	PackageName string
	Elapsed     float64
	Coverage    float64
	Outcome     string
	BuildOutput string
	TestResults []TestResult
}

func NewPackageResult(packageName string) *PackageResult {
	self := new(PackageResult)
	self.PackageName = packageName
	self.TestResults = []TestResult{}
	self.Coverage = -1
	return self
}

type TestResult struct {
	TestName string
	Elapsed  float64
	Passed   bool
	Skipped  bool
	File     string
	Line     int
	Message  string
	Error    string
	Stories  []reporting.ScopeResult

	RawLines []string `json:",omitempty"`
}

func NewTestResult(testName string) *TestResult {
	self := new(TestResult)
	self.Stories = []reporting.ScopeResult{}
	self.RawLines = []string{}
	self.TestName = testName
	return self
}

func resolvePackageName(path string) string {
	pkg, err := build.ImportDir(path, build.FindOnly)
	if err == nil {
		return pkg.ImportPath
	}

	nameArr := strings.Split(path, endGoPath)
	return nameArr[len(nameArr)-1]
}

const (
	separator = string(filepath.Separator)
	endGoPath = separator + "src" + separator
)
<|endoftext|>